Spring AOP 源码解析系列,建议你们按顺序阅读,欢迎讨论正则表达式
Spring AOP的实现从Spring自身的实现到集成AspectJ的实现,从硬编码到xml配置再到注解的方式,都是随着Spring的更新而不断演进。这一章我将介绍多种不一样的实现方式,既为Spring AOP的实现及配置作一个粗略的指南,同时为后续源码的解析作一个引子。spring
以以前的浏览器举例,有一个Browser接口chrome
public interface Browser { void visitInternet(); }
它的实现ChromeBrowserexpress
public class ChromeBrowser implements Browser{ public void visitInternet() { System.out.println("visit YouTube"); } }
众所周知,为了更自由的上网,须要一个境外服务器做为中转,这里就经过加密(encrypt)和解密(decrypt)两个方法模拟visitInternet方法执行先后的额外动做。浏览器
// 加密 private void encrypt(){ System.out.println("encrypt ..."); } // 解密 private void decrypt(){ System.out.println("decrypt ..."); }
而真正访问时经过一个代理类来操做,使用Spring AOP最原始也是最底层的方式ProxyFactory来实现。另外还须要封装上面两个方法的加强类,分别实现Spring定义的MethodBeforeAdvice和AfterReturningAdvice两个Advice加强接口。服务器
public class BrowserBeforeAdvice implements MethodBeforeAdvice{ public void before(Method method, Object[] args, Object target) throws Throwable { encrypt(); } //加密 private void encrypt(){ System.out.println("encrypt ..."); } } public class BrowserAfterReturningAdvice implements AfterReturningAdvice{ public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { decrypt(); } //解密 private void decrypt(){ System.out.println("decrypt ..."); } }
咱们使用硬编码的方式来实现代理架构
public class ProxyFactoryTest { public static void main(String[] args) { // 1.建立代理工厂 ProxyFactory factory = new ProxyFactory(); // 2.设置目标对象 factory.setTarget(new ChromeBrowser()); // 3.设置代理实现接口 factory.setInterfaces(new Class[]{Browser.class}); // 4.添加前置加强 factory.addAdvice(new BrowserBeforeAdvice()); // 5.添加后置加强 factory.addAdvice(new BrowserAfterReturningAdvice()); // 6.获取代理对象 Browser browser = (Browser) factory.getProxy(); browser.visitInternet(); } }
以上的前置加强和后置加强能够经过环绕加强统一处理,不过须要实现org.aopalliance.intercept.MethodInterceptor,它并非Spring定义的接口,而是来自AOP联盟提供的API。框架
public class BrowserAroundAdvice implements MethodInterceptor{ public Object invoke(MethodInvocation invocation) throws Throwable { encrypt(); Object retVal = invocation.proceed(); decrypt(); return retVal; } // 加密 private void encrypt(){ System.out.println("encrypt ..."); } // 解密 private void decrypt(){ System.out.println("decrypt ..."); } }
上面的学习
// 3.添加前置加强 factory.addAdvice(new BrowserBeforeAdvice()); // 4.添加后置加强 factory.addAdvice(new BrowserAfterReturningAdvice());
能够改成测试
// 添加环绕加强 factory.addAdvice(new BrowserAroundAdvice());
另外在上面的测试类中,并无加强(Advice)类的做用范围,也就是说只要Browser接口中的方法都会被代理。若是在Browser接口中增长一个听音乐的方法。
public interface Browser { void visitInternet(); void listenToMusic(); } public class ChromeBrowser implements Browser{ public void visitInternet() { System.out.println("visit YouTube"); } public void listenToMusic(){ System.out.println("listen to Cranberries"); } }
而我只想对visitInternet进行代理,能够经过正则表达式的切面类RegexpMethodPointcutAdvisor来设置,其内部使用的Pointcut类为JdkRegexpMethodPointcut。
// 建立正则表达式切面类 RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(); // 添加环绕加强 advisor.setAdvice(new BrowserAroundAdvice()); // 设置切入点正则表达式 advisor.setPattern("com.lcifn.spring.aop.bean.ChromeBrowser.visitInternet");
完整的测试类
public class RegexpProxyFactoryTest { public static void main(String[] args) { // 1.建立代理工厂 ProxyFactory factory = new ProxyFactory(); // 2.设置目标对象 factory.setTarget(new ChromeBrowser()); // 3.设置代理实现接口 factory.setInterfaces(new Class[]{Browser.class}); // 4.建立正则表达式切面类 RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(); // 5.添加环绕加强 advisor.setAdvice(new BrowserAroundAdvice()); // 6.设置切入点正则表达式 advisor.setPattern("com.lcifn.spring.aop.bean.ChromeBrowser.visitInternet"); // 7.工厂增长切面 factory.addAdvisor(advisor); // 8.获取代理对象 Browser browser = (Browser) factory.getProxy(); browser.visitInternet(); } }
毕竟硬编码的方式过于繁琐,也不适合项目的开发,仍是配置化的方式更加便捷。
在写AOP概念的时候,看Spring的官方文档中对其AOP的定位不是要作最强大的AOP实现,而是经过与IOC容器的结合从而达到便捷的使用。ProxyFactoryBean实现了FactoryBean接口(关于FactoryBean见FactoryBean),从而完美地结合了AOP与IOC。
来看简单的例子
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 原始对象 --> <bean id="chromeBrowser" class="com.lcifn.spring.aop.bean.ChromeBrowser"/> <!-- 环绕加强对象 --> <bean id="browserAroundAdvice" class="com.lcifn.spring.aop.advice.BrowserAroundAdvice"></bean> <bean id="browserProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 接口 --> <property name="interfaces" value="com.lcifn.spring.aop.bean.Browser"/> <!-- 要代理的对象 --> <property name="target" ref="chromeBrowser"/> <!-- 拦截器组 --> <property name="interceptorNames"> <list> <value>browserAroundAdvice</value> </list> </property> </bean> </beans>
ProxyFactoryBean至关于ProxyFactory实现了FactoryBean接口,经过IOC动态地建立代理对象。主要配置的属性有:
测试类以下:
public class ProxyFactoryBeanTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("aop/proxyfactorybean.xml"); Browser browser = (Browser) context.getBean("browserProxy"); browser.visitInternet(); } }
能够发现以上全部的代理都是经过接口的方式来接收,也就是说,底层是经过JDK自带的Proxy生成的代理。可是它的代理只能基于接口,若是想对未在接口中定义的方法或者类自己就没有实现接口的方法进行代理,那就要使用CGLIB的方式了。
在ChromeBrowser中增长一个非接口定义的方法
public String seeMovie(String movie){ System.out.println("see a movie:" + movie); return movie + " has bean seen"; }
经过正则表达式去匹配此方法进行代理,XML配置以下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 原始对象 --> <bean id="chromeBrowser" class="com.lcifn.spring.aop.bean.ChromeBrowser"/> <!-- 环绕加强对象 --> <bean id="browserAroundAdvice" class="com.lcifn.spring.aop.advice.BrowserAroundAdvice"></bean> <!-- 切面 --> <bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="browserAroundAdvice"></property> <!-- 切入点正则表达式 --> <property name="pattern" value="com.lcifn.spring.aop.bean.ChromeBrowser.seeMovie"></property> </bean> <bean id="browserProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- 要代理的对象 --> <property name="target" ref="chromeBrowser"/> <!-- 拦截器组 --> <property name="interceptorNames" value="regexpAdvisor"/> <!-- proxyTargetClass --> <property name="proxyTargetClass" value="true"></property> </bean> </beans>
对ProxyFactoryBean的配置新增proxyTargetClass属性,网上对此属性的解释是强制使用CGLIB代理对象,而在Spring的文档中对此的解释则是
force proxying for the TargetSource's exposed target class. If that target class is an interface, a JDK proxy will be created for the given interface. If that target class is any other class, a CGLIB proxy will be created for the given class.
即强制暴露TargetSource中的目标class,若是此class是接口,则使用JDK代理,若是是类对象,则使用CGLIB代理。可是正常状况下,都是使用CGLIB代理。
来看测试类
public class RegexpProxyFactoryBeanTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("aop/proxyfactorybean-regexp.xml"); ChromeBrowser browser = (ChromeBrowser) context.getBean("browserProxy"); browser.seeMovie("The Great Wall"); } }
此时已经能解决大部分的问题了,但AOP所处理的就是多个业务中类似的非逻辑相关的问题。于是ProxyFactoryBean的配置会有不少,太多的XML配置总会很麻烦。Spring设计时也考虑到这个问题,于是有了自动代理。
自动代理,即自动发现Advisor(切面)配置,意味着再也不须要一个个地配置ProxyFactoryBean,只须要配置特定的切面便可。来看配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 原始对象 --> <bean id="chromeBrowser" class="com.lcifn.spring.aop.bean.ChromeBrowser"/> <!-- 环绕加强对象 --> <bean id="browserAroundAdvice" class="com.lcifn.spring.aop.advice.BrowserAroundAdvice"></bean> <!-- 切面 --> <bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="browserAroundAdvice"></property> <!-- 切入点正则表达式 --> <property name="pattern" value="com.lcifn.spring.aop.bean.ChromeBrowser.visit.*"></property> </bean> <!-- 自动扫描切面代理类 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <property name="optimize" value="true"></property> </bean> </beans>
此时若是增长一种AOP逻辑,只须要配置一个新的切面类,指定要代理的切入点和加强类便可。自动代理的测试同ProxyFactoryBean相同,就不展现了。
这里涉及到一个新的属性optimize,此属性在使用JDK仍是CGLIB代理的判断上同proxyTargetClass一致。但其文档上阐述了一些其余信息。
Set whether proxies should perform aggressive optimizations.The exact meaning of "aggressive optimizations" will differ between proxies, but there is usually some tradeoff.
用来设置代理时是否使用激进的优化策略。但不一样的代理间的优化策略也不相同,一般状况只是一种权衡。
For example, optimization will usually mean that advice changes won't take effect after a proxy has been created. For this reason, optimization is disabled by default. An optimize value of "true" may be ignored if other settings preclude optimization: for example, if "exposeProxy" is set to "true" and that's not compatible with the optimization.
好比,优化一般意味着对于已经生成的代理,加强(Advice)的变化没法对其产生影响。鉴于此,默认优化配置是禁止的。另外若是其余配置阻止了优化策略的,optimize=true将被忽略。好比exposeProxy=true与优化策略是不兼容的。
将自动代理类DefaultAdvisorAutoProxyCreator的optimize属性设置为true,是由于并不清楚代理的切面是什么状况,于是须要Spring帮助咱们对各类状况作一些权衡。
作到这里,当年Spring的罗大侠以为这下应该知足大家这群用户了吧。但是不少用户又提出新的问题:业务愈来愈复杂,咱们须要更加精细的控制。另外JDK5的出现让人们意识到注解相比于XML更简洁。所以,罗大侠又说,ok,都知足大家,集成AspectJ,支持注解,大家满意了吧。
Spring3.0的发布经过配置类让XML配置能够完美地被取代,而AOP的配置也能够经过注解的方式更加便捷的设置。下面还以浏览器举例,来看注解的AOP如何配置。
@Component [@Aspect](https://my.oschina.net/aspect) public class AspectJAnnotationBrowserAroundAdvice { @Pointcut("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(..))") private void pointcut(){ } @Around(value="pointcut()") public Object aroundIntercept(ProceedingJoinPoint pjp) throws Throwable{ encrypt(); Object retVal = pjp.proceed(); decrypt(); return retVal; } // 加密 private void encrypt(){ System.out.println("encrypt ..."); } // 解密 private void decrypt(){ System.out.println("decrypt ..."); } }
好比
@Around("execution(* com.lcifn.spring.aop.bean.ChromeBrowser.*(..))")
类上的@Component注解表示它被Spring所管理。固然要使这些注解生效,也须要启用相关配置,来看配置类。
@Configuration @ComponentScan("com.lcifn.spring.aop.bean,com.lcifn.spring.aop.advice") @EnableAspectJAutoProxy(proxyTargetClass=true) public class AppConfig { }
没有了XML配置,启动Spring容器固然不能再用ClassPathXmlApplicationContext类,而是使用AnnotationConfigApplicationContext。在建立对象时传入配置类的Class对象做为参数。
public class AspectJAnnotationAopTest { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); ChromeBrowser browser = (ChromeBrowser) context.getBean("chromeBrowser"); browser.visitInternet(); browser.listenToMusic(); browser.seeMovie("The Great Wall"); } }
有人可能以为由于历史的缘故或其余缘由,不想使用AspectJ注解配置AOP,而是经过XML配置,可不能够呢?Spring也考虑到此种状况了。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 原始对象 --> <bean id="chromeBrowser" class="com.lcifn.spring.aop.bean.ChromeBrowser"/> <!-- 环绕加强对象 --> <bean id="aspectjBrowserAroundAdvice" class="com.lcifn.spring.aop.advice.AspectJBrowserAroundAdvice"></bean> <!-- aspectj aop 配置 --> <aop:config> <!-- 切入点配置 --> <aop:pointcut id="browserPointcut" expression="execution(* com.lcifn.spring.aop.bean.*.*(..))"/> <aop:aspect ref="aspectjBrowserAroundAdvice"> <!-- 环绕加强 --> <aop:around method="aroundIntercept" pointcut-ref="browserPointcut" /> </aop:aspect> </aop:config> </beans>
经过aop:config标签及其子标签配置,其中aop:pointcut切入点的配置能够和aop:aspect同级,这样能够被多个aspect重复使用,也能够配置再aop:aspect内部,只被单个aspect使用。
若是存在外部的advice配置,好比事务管理的tx:advice,则能够经过aop:advisor进行整合。这里也不详细介绍了。
上面介绍了纯注解和纯XML两种方式,但实际项目中每每是简单的XML+注解的方式。AOP的配置使用注解,同纯注解中的AspectJAnnotationBrowserAroundAdvice类一致,而不使用配置类,启用XML配置的方式。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 类扫描 --> <context:component-scan base-package="com.lcifn.spring.aop.bean,com.lcifn.spring.aop.advice"/> <!-- 启用AspectJ注解 --> <aop:aspectj-autoproxy/> </beans>
能够发现此时的XML配置变得特别的简单且能兼容历史,而注解使用也是便捷,于是不少人喜欢采用此种方式也就能够理解。
对于Spring AOP的加强,本文都是采用AroundAdvice环绕加强来举例,对于其余的加强以他人的一个表格简单总结下。而对于引入加强(IntroductionAdvice)后续会有单独的章节介绍。
加强类型 | 基于 AOP 接口 | 基于 @Aspect | 基于 aop:config |
---|---|---|---|
Before Advice(前置加强) | MethodBeforeAdvice | @Before | aop:before |
AfterReturningAdvice(后置加强) | AfterReturningAdvice | @AfterReturning | aop:after-returning |
AfterAdvice(Finally加强) | 无 | @After | aop:after |
AroundAdvice(环绕加强) | MethodInterceptor | @Around | aop:around |
ThrowsAdvice(抛出加强) | ThrowsAdvice | @AfterThrowing | aop:after-throwing |
IntroductionAdvice(引入加强) | DelegatingIntroductionInterceptor | @DeclareParents | aop:declare-parents |
本文介绍了Spring AOP的各类实现,从ProxyFactory, 到ProxyFactoryBean,再到自动代理DefaultAdvisorAutoProxyCreator,最后到与AspectJ的结合。如今可能不多人会使用前三种方式来配置AOP,但了解这些实现可以帮助咱们更好地理解Spring AOP的实现原理和架构设计。**咱们研究一个框架的使用,实现乃至于源码,更多地应该理解它的总体架构以及设计理念,尤为是一个设计优秀的框架。**但愿经过对优秀的框架地学习,来提高本身的编码水平甚至软件架构水平。后面的章节会深刻Spring AOP的源码,但愿经过对它的学习可以进一步地提高本身,也但愿看文章的同窗们也可以同我一块儿加油!
参考文档: