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的源碼,但願經過對它的學習可以進一步地提高本身,也但願看文章的同窗們也可以同我一塊兒加油!
參考文檔: