spring源碼學習--AOP初探
LZ以前一直覺得,學習spring源碼,起碼要把人家的代碼整體上通讀一遍,現在想想這是很愚蠢的,spring作為一個應用平臺,不是那麽好研究透徹的,而且也不太可能有人把spring的源碼全部清楚的過上一遍,哪怕是spring的締造者。我們確實沒有必要把源碼全部過一遍,如果在看spring源碼的過程中,能學習到東西是最關鍵的,說實話,下面的很多東西也是我邊看源碼,邊看大神的解讀寫出來的,基本不屬於原創,但是改變自己的思想,盡量從源碼出發,不要一出現問題就只能寄所有希望於百度,如果能做到這點,最好不過了。
AOP中文翻譯是面向切面編程,與面向對象,面向接口,面向服務等概念是相似的,所謂面向切面,即是使用切面與其他事物產生關系。面向對象強調一切皆對象,也可以說面向接口是一切皆接口,面向服務是一切皆服務,而面向切面也是一樣,一切皆切面。
下面我們具體說說AOP,目前由AOP聯盟給出了AOP的標準,AOP聯盟的規範只是提供了幾個接口定義,為了統一AOP的標準,下面來看看主要的幾個接口的uml類圖。
Advice接口:這是一個空接口,裏面沒有任何方法,只是用來標識一個通知。LZ自己的理解,這個接口定義了要通知什麽內容。
Interceptor接口:Advice的子接口,這個接口和advice都不直接使用,一般是要擴展以後去實現特殊的攔截。
Joinpoint接口:代表了一個運行時的連接點。
Invocation接口:代表了程序中的一個調用,可以被攔截器Interceptor攔截。
下面還有幾個接口,分別是Interceptor和Invocation的擴展接口,從下一層繼承開始,interpretor的繼承體系已經開始依賴於invocation。這從某種意義上來說,advice是依賴於joinpoint的。
以上就是AOP聯盟規範裏的的幾個主要接口。下面我們看一下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,interpretorNames代表需要加強哪些東西以及需要怎樣加強,也就是pointcut和advice。targetName代表的則是我們針對誰來做這些加強,即我們的目標對象。
首先interpretorNames是必須賦予的屬性,這個屬性指定了通知器或者是通知的名字。如果傳入的是通知Advice,則會被自動包裝為通知器。
targetName是我們要增強的目標對象,這個對象如果有實現的接口,則會采用JDK的動態代理實現,否則將需要第三方的jar包cglib。
下面我們測試一下spring AOP是不是真的可以給目標對象加額外功能。我們首先在spring的容器裏添加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" 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>
下面是目標對象TestTarget。
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(); } }
通知器裏自定義的通知(advice)和切點(pointcut),分別代表通知什麽和在什麽地方通知。首先是通知。
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() + "()"); } }
下面是切點。
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; } }; } }
切點只在目標對象的test方法執行完後打印一下。代碼就這些,我們來測試一下。
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(); } }
打印結果。
我們增強的afterReturningAdvice已經起作用了。這裏只是演示一下spring aop的實現,實際開發中不要這麽用。
我相信很多人都能看懂以上代碼邏輯,下一篇文章,我們順著spring源碼,看一下spring到底是如何實現的以上邏輯。
spring源碼學習--AOP初探