作者:zuoxiaolong8810(左瀟龍),轉載請注明出處,特別說明:本博文來自博主原博客,為保證新博客中博文的完整性,特復制到此留存,如需轉載請注明新博客地址即可。
最近工作很忙,但當初打算學習spring源碼的事已經基本告一段落,只是一直沒時間寫這些記錄性的文字。
本次鄙人學習spring的源碼,有點囫圇吞棗的感覺,其實這樣並非就不好,spring作為一個應用平台,自然不是那么好研究透徹的,而且也不太可能有人把spring的源碼全部清楚的過上一遍,哪怕是spring的締造者。不過最主要的原因是我們確實沒有必要把源碼全部過一遍。
在看spring源碼的過程中,學習到了很多東西,這就足夠了,而且現在如果在使用spring的過程中出現任何問題,不會再像以前一樣有時候會束手無策,甚至把全部希望寄托於度娘,這其實是不正確的思想。從源碼出發,可以發現,其實以前覺得無頭緒的問題,現在都會迎刃而解。這就是研究源碼最大的收獲吧。
言歸正傳,來說說AOP。AOP的中文翻譯是面向切面編程,與面向對象,面向接口,面向服務等概念看起來挺相似的。其實最近一直看有關JAVA的書籍,多多少少也對於這些有些理解,所謂面向切面,即是使用切面與其它事物產生關系。面向對象強調一切皆對象,也可以說面向接口是一切皆接口,面向服務則是一切皆服務,而切面也是一樣,一切皆切面。
為什么這么說,面向對象就不說了,就拿面向接口來說吧,強調的就是我們操縱的都是一些接口,所以我們永遠不會依賴於實現,面向切面的思想,其實也很類似,即我們操縱的都是一些切面,不會依賴於流程。也正是因為如此,所以我們可以優雅的解決WEB應用中一些原本很棘手或者不好處理的問題。
以上都是一些理論性的東西,也都是鄙人自己最近的感悟,雖然不知對錯,但我覺得這種感悟很重要。
下面我們具體說說AOP,目前由AOP聯盟給出了AOP的標准,AOP聯盟的規范只是提供了幾個接口定義,為了統一AOP的標准,下面來看看主要的幾個接口。
Advice接口:
這是一個空接口,里面沒有任何方法,只是用來標識一個通知,在spring中,所有的advice都是此接口的擴展,比如BeforeAdvice,AfterAdvice等。
Interceptor接口:
Advice的子接口,這個接口和advice都不直接使用,一般是要擴展以后去實現特殊的攔截。
Joinpoint接口:
代表了一個運行時的連接點。
Invocation接口:
代表了程序中的一個調用,可以被攔截器Interceptor攔截。
下面還有幾個接口,不再一一介紹,從下一層繼承開始,interpretor的繼承體系已經開始依賴於invocation。這從某種意義上來說,advice是依賴於joinpoint的,但這並非絕對。
下面給出這幾個接口的UML圖,關系比較清晰。
下面介紹下spring中的AOP核心接口。
Advice體系:
Spring采用AOP聯盟的Advice作為超級接口,擴展了很多子接口,比如BeforeAdvice,AfterAdvice等等,稍后以AfterReturningAdvice為例,討論下spring的通知體系。
Pointcut接口:
spring采用Pointcut作為切點的抽象,其中有一個方法返回一個MethodMatcher,作用很明顯,就是說切點決定了要切入哪些方法。這里其實是定義了一個匹配規則。比如正則匹配,可以只匹配前綴為save的方法等等。
Advisor:
通知器或者說通知者,我們從現實角度來說,通知者當然需要知道要通知什么。所以Advisor依賴於Advice,而Advisor旗下的子接口PointAdvisor還依賴於Pointcut,也就是說這個接口更確切的定位應該是包含了要通知誰和要通知什么,也就是說要能獲得Advice和Pointcut。
下面我們先用一個例子說明spring的AOP是如何工作的,在spring的bean中有一種特殊的bean,叫FactoryBean。這並不是一個普通的bean,而是用來產生bean的一個bean。這樣說起來有點繞口,但這個接口就是用來做這個事的,它是為了實現一系列特殊加工過的bean而產生的接口。
比如AOP中,我們其實就是要對一個bean進行增強,進行加工,讓它在運行的過程中可以做一些特殊的事情。
ProxyFactoryBean就是一個為了AOP實現的特殊的FactoryBean,它的作用很明顯就是產生proxy的bean,也就是被我們增強過的bean,在這里給出它的源碼。
public class ProxyFactoryBean extends ProxyCreatorSupport implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware { /** * This suffix in a value in an interceptor list indicates to expand globals. */ public static final String GLOBAL_SUFFIX = "*"; protected final Log logger = LogFactory.getLog(getClass()); private String[] interceptorNames; private String targetName; private boolean autodetectInterfaces = true; private boolean singleton = true; private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); private boolean freezeProxy = false; private transient ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader(); private transient boolean classLoaderConfigured = false; private transient BeanFactory beanFactory; /** Whether the advisor chain has already been initialized */ private boolean advisorChainInitialized = false; /** If this is a singleton, the cached singleton proxy instance */ private Object singletonInstance;
在這里,一樣沒有貼出全部的源碼,我們主要關心它的屬性,其中interpretorNames和targetName就是這個類最主要的屬性,前者代表的是需要加強哪些東西以及需要怎樣加強,也就是advice和pointcut。而后者代表的則是我們針對誰來做這些加強,即我們的目標對象。
這里面的增強比較靈活,我來說一下需要的屬性。
1.首先interpretorNames是必須賦予的屬性,這個屬性指定了通知器或者是通知的名字。如果傳入的是通知Advice,則會被自動包裝為通知器。
2.targetName是我們要增強的目標對象,這個對象如果有實現的接口,則會采用JDK的動態代理實現,否則將需要第三方的jar包cglib。
下面給出一段代碼,來測試一下這種增強方式。首先我們需要一個bean.xml的配置文件,去配置這些屬性。這樣就將IOC和AOP結合起來使用了。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="testAdvisor" class="com.springframework.aop.test.TestAdvisor"></bean> <bean id="testTarget" class="com.springframework.aop.test.TestTarget"></bean> <bean id="testAOP" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetName"> <value>testTarget</value> </property> <property name="interceptorNames"> <list> <value>testAdvisor</value> </list> </property> </bean> </beans>
下面我們看看目標對象的源碼。
public class TestTarget{ public void test() { System.out.println("target.test()"); } public void test2(){ System.out.println("target.test2()"); } }
很簡單,只有兩個普通的方法,下面我們看通知器的源碼。
public class TestAdvisor implements PointcutAdvisor{ public Advice getAdvice() { return new TestAfterAdvice(); } public boolean isPerInstance() { return false; } public Pointcut getPointcut() { return new TestPointcut(); } }
我使用的是包含了切點的通知器,所以在這里我們還要給出通知和切點,我寫了兩個簡單的通知和切點。
public class TestAfterAdvice implements AfterReturningAdvice{ public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("after " + target.getClass().getSimpleName() + "." + method.getName() + "()"); } }
下面是切點,切點的描述是我們只增強test方法,不增強test2方法。
public class TestPointcut implements Pointcut{ public ClassFilter getClassFilter() { return ClassFilter.TRUE; } public MethodMatcher getMethodMatcher() { return new MethodMatcher() { public boolean matches(Method method, Class<?> targetClass, Object[] args) { if (method.getName().equals("test")) { return true; } return false; } public boolean matches(Method method, Class<?> targetClass) { if (method.getName().equals("test")) { return true; } return false; } public boolean isRuntime() { return true; } }; } }
好了,下面我們的准備工作都已經做好了。來看一下見證奇跡的時刻吧,我們使用IOC容器來幫我們完成增強,以下這段代碼的運行需要cglib,測試代碼如下:
public class TestAOP { public static void main(String[] args) { ApplicationContext applicationContext = new FileSystemXmlApplicationContext("classpath:beans.xml"); TestTarget target = (TestTarget) applicationContext.getBean("testAOP"); target.test(); System.out.println("------無敵分割線-----"); target.test2(); } }
輸入結果會發現,在target.test方法執行后,我們增強的afterReturningAdvice已經起作用了,但是我們將切點定義在了test方法,所以test2方法並沒有被增強。
這一章主要是和各位一起感受一下springAOP的神奇,下一章,將會是spring學習之路的最后一章,我會和各位一起看一下上述我們的增強過程是如何完成的。