Spring中AOP相關的API及原始碼解析
本系列文章:
談談Spring中的物件跟Bean,你知道Spring怎麼建立物件的嗎?
推薦閱讀:
本系列文章將會帶你一行行的將Spring的原始碼吃透,推薦閱讀的文章是閱讀原始碼的基礎!
因為本文會涉及到動態代理的相關內容,如果對動態代理不是很瞭解的話,參考文章:
前言
之所以寫這麼一篇文章主要是因為下篇文章將結束Spring啟動整個流程的分析,從解析配置到建立物件再到屬性注入最後再將建立好的物件初始化成為一個真正意義上的Bean。因為下篇文章會設計到AOP,所以提前單獨將AOP的相關API及原始碼做一次解讀,這樣可以降低閱讀原始碼的障礙,話不多說,我們進入正文!
一個使用API建立代理的例子
在進入API分析前,我們先通過兩個例子體會下如何使用API的方式來建立一個代理物件,對應示例如下:
- 定義通知
public class DmzAfterReturnAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
System.out.println("after invoke method [" + method.getName() + "],aop afterReturning logic invoked");
}
}
public class DmzAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("aroundAdvice invoked");
return invocation.proceed();
}
}
public class DmzBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before invoke method [" + method.getName() + "],aop before logic invoked");
}
}
public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
@Override
public void run() {
System.out.println("running!!!!");
}
}
- 切點
public class DmzPointcut implements Pointcut {
@Override
@NonNull
public ClassFilter getClassFilter() {
// 在類級別上不進行攔截
return ClassFilter.TRUE;
}
@Override
@NonNull
public MethodMatcher getMethodMatcher() {
return new StaticMethodMatcherPointcut() {
@Override
public boolean matches(@NonNull Method method, Class<?> targetClass) {
// 對於toString方法不進行攔截
return !method.getName().equals("toString");
}
};
}
}
- 目標類
public class DmzService {
@Override
public String toString() {
System.out.println("dmzService toString invoke");
return "dmzService";
}
public void testAop(){
System.out.println("testAop invoke");
}
}
- 測試程式碼
public class Main {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
// 一個Advisor代表的是一個已經跟指定切點繫結了的通知
// 在這個例子中意味著環繞通知不會作用到toString方法上
Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice());
// 新增一個繫結了指定切點的環繞通知
proxyFactory.addAdvisor(advisor);
// 新增一個返回後的通知
proxyFactory.addAdvice(new DmzAfterReturnAdvice());
// 新增一個方法執行前的通知
proxyFactory.addAdvice(new DmzBeforeAdvice());
// 為代理類引入一個新的需要實現的介面--Runnable
proxyFactory.addAdvice(new DmzIntroductionAdvice());
// 設定目標類
proxyFactory.setTarget(new DmzService());
// 因為要測試代理物件自己定義的方法,所以這裡啟用cglib代理
proxyFactory.setProxyTargetClass(true);
// 建立代理物件
Object proxy = proxyFactory.getProxy();
// 呼叫代理類的toString方法,通過控制檯檢視代理邏輯的執行情況
proxy.toString();
if (proxy instanceof DmzService) {
((DmzService) proxy).testAop();
}
// 判斷引入是否成功,並執行引入的邏輯
if (proxy instanceof Runnable) {
((Runnable) proxy).run();
}
}
}
這裡我就不將測試結果放出來了,大家可以先自行思考這段程式將輸出什麼。接下來我們就來分析上面這段程式中所涉及到的API,通過這些API的學習相信大家可以徹底理解上面這段程式碼。
API介紹
Pointcut(切點)
對應介面定義如下:
public interface Pointcut {
// ClassFilter,在類級別進行過濾
ClassFilter getClassFilter();
// MethodMatcher,在方法級別進行過濾
MethodMatcher getMethodMatcher();
// 一個單例物件,預設匹配所有
Pointcut TRUE = TruePointcut.INSTANCE;
}
切點的主要作用是定義通知所要應用到的類跟方法,上面的介面定義也很明顯的體現了這一點,我們可以將其拆分成為兩個部分
ClassFilter
,介面定義如下:
public interface ClassFilter {
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
ClassFilter
的主要作用是在類級別上對通知的應用進行一次過濾,如果它的match方法對任意的類都返回true的話,說明在類級別上我們不需要過濾,這種情況下,通知的應用,就完全依賴MethodMatcher
的匹配結果。
MethodMatcher
,介面定義如下:
public interface MethodMatcher {
boolean matches(Method method, @Nullable Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
MethodMatcher
中一共有三個核心方法
matches(Method method, @Nullable Class<?> targetClass)
,這個方法用來判斷當前定義的切點跟目標類中的指定方法是否匹配,它可以在建立代理的時候就被呼叫,從而決定是否需要進行代理,這樣就可以避免每次方法執行的時候再去做判斷isRuntime()
,如果這個方法返回true的話,意味著每次執行方法時還需要做一次匹配matches(Method method, @Nullable Class<?> targetClass, Object... args)
,當之前的isRuntime
方法返回true時,會呼叫這個方法再次進行一次判斷,返回false的話,意味這個不對這個方法應用通知
Advice(通知)
環繞通知(Interception Around Advice)
介面定義如下:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
在上面介面定義的invoke
方法中,MethodInvocation
就是當前執行的方法,當我們呼叫invocation.proceed
就是在執行當前的這個方法,基於此,我們可以在方法的執行前後去插入我們自定義的邏輯,比如下面這樣
// 執行前的邏輯
doSomeThingBefore();
Object var = invocation.proceed;
doSomeThingAfter();
// 執行後的邏輯
retrun var;
前置通知(Before Advice)
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
跟環繞通知不同的是,這個介面中定義的方法的返回值是void
,所以前置通知是無法修改方法的返回值的。
如果在前置通知中發生了異常,那麼會直接終止目標方法的執行以及打斷整個攔截器鏈的執行
後置通知(After Returning Advice)
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
後置通知相比較於前置通知,主要有以下幾點不同
- 後置通知可以訪問目標方法的返回值,但是不能修改
- 後置通知是在方法執行完成後執行
異常通知(Throws Advice)
public interface ThrowsAdvice extends AfterAdvice {
}
異常通知中沒有定義任何方法,它更像一個標記介面。我們在定義異常通知時需要實現這個介面,同時方法的簽名也有要求
- 方法名稱必須是
afterThrowing
- 方法的引數個數必須是1個或者4個,如下:
public class OneParamThrowsAdvice implements ThrowsAdvice {
// 如果只有一個引數,那麼這個引數必須是要進行處理的異常
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
public class FourParamThrowsAdvice implements ThrowsAdvice {
// 如果定義了四個引數,那麼這四個引數分別是
// 1.m:目標方法
// 2.args:執行目標方法所需要的引數
// 3.target:目標物件
// 4.ex:具體要處理的異常
// 並且引數型別必須按照這個順序定義
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
我們可以在一個異常通知中定義多個方法,在後續的原始碼分析中我們會發現,這些方法最終會被註冊成對應的異常的handler,像下面這樣
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
引入通知(Introduction Advice)
引入通知的主要作用是可以讓生成的代理類實現額外的介面。例如在上面的例子中,我們為DmzService
建立一個代理物件,同時為其定義了一個引入通知
public class DmzIntroductionAdvice extends DelegatingIntroductionInterceptor implements Runnable {
@Override
public void run() {
System.out.println("running!!!!");
}
}
在這個引入通知中,我們為其引入了一個新的需要實現的介面Runnable
,同時通知本身作為這個介面的實現類。
通過這個引入通知,我們可以將生成的代理類強轉成Runnable
型別然後執行其run方法,同時,run方法也會被前面定義的前置通知,後置通知等攔截。
為了更好的瞭解引入通知,我們來需要了解下DelegatingIntroductionInterceptor
這個類。見名知意,這個類就是一個委託引入攔截器,因為我們要為代理類引入新的介面,因為著我們要提供具體的實現的邏輯,而具體的實現的邏輯就可以被委託給這個DelegatingIntroductionInterceptor
。
我們可以看看它的原始碼
public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
implements IntroductionInterceptor {
// 實際實現了引入邏輯的類
@Nullable
private Object delegate;
// 對外提供了一個帶參的建構函式,通過這個建構函式我們可以傳入一個
// 具體的實現類
public DelegatingIntroductionInterceptor(Object delegate) {
init(delegate);
}
// 對子類暴露了一個空參的建構函式,預設將自身作為實現了引入邏輯的委託類
// 我們上面的例子中就是使用的這種方法
protected DelegatingIntroductionInterceptor() {
init(this);
}
// 對這個類進行初始化,要通過實際的實現類來找到具體要實現的介面
private void init(Object delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
// 找到delegate所有實現的介面
implementInterfacesOnObject(delegate);
// 因為我們可能會將DelegatingIntroductionInterceptor本身作為委託者
// Spring的設計就是不對外暴露這兩個介面
// 如果將其暴露,意味著我們可以將代理類強轉成這種型別
suppressInterface(IntroductionInterceptor.class);
suppressInterface(DynamicIntroductionAdvice.class);
}
// 引入通知本身也是基於攔截器實現的,當執行一個方法時需要判斷這個方法
// 是不是被引入的介面中定義的方法,如果是的話,那麼不能呼叫目標類的方法
// 而要呼叫委託類的方法
public Object invoke(MethodInvocation mi) throws Throwable {
if (isMethodOnIntroducedInterface(mi)) {
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());
// 這裡是處理一種特殊情況,方法的返回值是this的時候
// 這裡應該返回代理類
if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
Object proxy = ((ProxyMethodInvocation) mi).getProxy();
if (mi.getMethod().getReturnType().isInstance(proxy)) {
retVal = proxy;
}
}
// 其餘情況下直接將委託類的執行結果返回
return retVal;
}
// 執行到這裡說明不是引入的方法,這是Spring提供了一個擴充套件邏輯
// 正常來說這個類只會處理引入的邏輯,通過這個方法可以對目標類中的方法做攔截
// 不常用
return doProceed(mi);
}
protected Object doProceed(MethodInvocation mi) throws Throwable {
return mi.proceed();
}
}
通過檢視這個類的原始碼我們可以發現,所謂的引入其實就是在方法執行的時候加了一層攔截,當判斷這個方法是被引入的介面提供的方法的時候,那麼就執行委託類中的邏輯而不是目標類中的方法
關於通知的總結
通過上文的分析我們可以發現,通知總共可以分為這麼幾類
- 普通的通知(前置,後置,異常等,沒有實現
MethodInterceptor
介面) - 環繞通知(實現了
MethodInterceptor
介面) - 引入通知(需要提供額外的引入的資訊,實現了
MethodInterceptor
介面)
上面的分類並不標準,只是為了方便大家記憶跟理解,雖然我們普通的通知沒有直接實現MethodInterceptor
介面,但其實它的底層也是依賴於攔截器來完成的,大家可以看看下面這個類
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
// 根據傳入的一個前置通知,建立一個對應的攔截器
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
private final MethodBeforeAdvice advice;
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
// 實際上還是利用攔截器,在方法執行前呼叫了通知的before方法完成了前置通知
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}
Advisor (繫結通知跟切點)
一個Advisor
實際上就是一個繫結在指定切點上的通知。在前面的例子我們可以發現,有兩種新增通知的方式
// 一個Advisor代表的是一個已經跟指定切點繫結了的通知
// 在這個例子中意味著環繞通知不會作用到toString方法上
Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice());
// 新增一個繫結了指定切點的環繞通知
proxyFactory.addAdvisor(advisor);
// 新增一個返回後的通知
proxyFactory.addAdvice(new DmzAfterReturnAdvice());
一種是直接新增了一個Advisor
,還有一種是新增一個Advice
,後者也會被轉換成一個Advisor
然後再進行新增,沒有指定切點的通知是沒有任何意義的
public void addAdvice(Advice advice) throws AopConfigException {
int pos = this.advisors.size();
// 預設新增到集合的最後一個位置
addAdvice(pos, advice);
}
// 這個方法新增通知
public void addAdvice(int pos, Advice advice) throws AopConfigException {
Assert.notNull(advice, "Advice must not be null");
// 如果是一個引入通知,那麼構建一個DefaultIntroductionAdvisor
// DefaultIntroductionAdvisor會匹配所有類
if (advice instanceof IntroductionInfo) {
addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
}
// 不能直接新增一個不是IntroductionInfo的DynamicIntroductionAdvice(動態引入通知)
else if (advice instanceof DynamicIntroductionAdvice) {
throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
}
else {
// 如果是普通的通知,那麼會建立一個DefaultPointcutAdvisor
// DefaultPointcutAdvisor所定義的切點會匹配所有類以及所有方法
addAdvisor(pos, new DefaultPointcutAdvisor(advice));
}
}
ProxyCreatorSupport
這個類的主要作用是為建立一個AOP代理物件提供一些功能支援,通過它的getAopProxyFactory
能獲取一個建立代理物件的工廠。
// 這裡我只保留了這個類中的關鍵程式碼
public class ProxyCreatorSupport extends AdvisedSupport {
private AopProxyFactory aopProxyFactory;
// 空參構造,預設會建立一個DefaultAopProxyFactory
// 通過這個ProxyFactory可以建立一個cglib代理或者jdk代理
public ProxyCreatorSupport() {
this.aopProxyFactory = new DefaultAopProxyFactory();
}
// 通過這個方法可以建立一個具體的代理物件
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
// 實際就是使用DefaultAopProxyFactory來建立一個代理物件
// 可以看到在呼叫createAopProxy方法時,傳入的引數是this
// 這是因為ProxyCreatorSupport本身就儲存了建立整個代理物件所需要的配置資訊
return getAopProxyFactory().createAopProxy(this);
}
}
另外通過上面的UML類圖還能看到,ProxyCreatorSupport
繼承了AdvisedSupport
,AdvisedSupport
繼承了ProxyConfig
。
ProxyConfig
其中ProxyConfig
是所有的AOP代理工廠的父類,它包含了建立一個AOP代理所需要的基礎的通用的一些配置資訊
// 這裡省略了一些getter跟setter方法
public class ProxyConfig implements Serializable {
// 是否開啟cglib代理,預設不開啟使用jdk動態代理
private boolean proxyTargetClass = false;
// 是否啟用優化,預設為false,按照官網對這個引數的解釋
// 這個優化是針對cglib,如果設計為true的話,會做一些侵入性的優化
// 是否開啟在jdk代理的情況下沒有影響
// 官網中特地說明了,除非對cglib的優化非常瞭解,否則不要開啟這個引數
private boolean optimize = false;
// 生成的代理類是否需要實現Advised介面,這個介面可以向外提供操作通知的方法
// 如果為false會實現
// 為true的話,不會實現
boolean opaque = false;
// 是否將當前的配置類暴露到一個執行緒上下文中,如果設定為true的話
// 可以通過AopContext.currentProxy()來獲取到當前的代理物件
boolean exposeProxy = false;
// 標誌著是否凍結整個配置,如果凍結了,那麼配置資訊將不允許修改
private boolean frozen = false;
}
AdvisedSupport
當我們為某個物件建立代理時,除了需要上面的ProxyConfig
提供的一些基礎配置外,起碼還需要知道
- 需要執行的通知是哪些?
- 目標物件是誰?
- 建立出來的代理需要實現哪些介面?
而這些配置資訊是由AdvisedSupport
提供的,AdvisedSupport
本身實現了Advised
介面,Advised
介面定義了管理通知的方法。
在瞭解了上面的API後我們來看看Spring提供了幾種建立AOP代理的方式
- ProxyFactoryBean
- ProxyFactory
- Auto-proxy
ProxyFactoryBean的方式建立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.xsd">
<bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>
<bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>
<bean id="dmzProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!---->
<property name="proxyInterfaces" value="java.lang.Runnable"/>
<property name="proxyTargetClass" value="true"/>
<property name="target" ref="dmzService"/>
<property name="interceptorNames">
<list>
<value>aroundAdvice</value>
</list>
</property>
</bean>
</beans>
// 目標類
public class DmzService {
@Override
public String toString() {
System.out.println("dmzService toString invoke");
return "dmzService";
}
public void testAop(){
System.out.println("testAop invoke");
}
}
// 通知
public class DmzAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("aroundAdvice invoked");
return invocation.proceed();
}
}
public class SourceMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application-init.xml");
DmzService dmzProxy = ((DmzService) cc.getBean("dmzProxy"));
dmzProxy.testAop();
}
}
ProxyFactoryBean介紹
跟普通的FactoryBean
一樣,這個類的主要作用就是通過getObject
方法能夠獲取一個Bean,不同的是這個類獲取到的是代理後的Bean。
我們檢視這個類的繼承關係可以發現
這個類除了實現了FactoryBean
介面以及一些Aware
介面外,額外還繼承了ProxyCreatorSupport
類。它是一個factoryBean
,所以我們重點就關注它的getObject
方法即可。
public Object getObject() throws BeansException {
// 初始化通知鏈
// 這裡主要就是將在XML中配置的通知新增到
// AdvisedSupport管理的配置中去
initializeAdvisorChain();
if (isSingleton()) {
// 如果是單例的,那麼獲取一個單例的代理物件
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
// 如果是原型的,獲取一個原型的代理物件
return newPrototypeInstance();
}
}
關於這段程式碼就不做過多分析了,它其實就兩步(不管是哪種方式建立代理,都分為這兩步)
- 完善建立代理需要的配置資訊
- 建立代理
其中配置資訊分為兩部分,其一是AppConfig
管理的通用的配置資訊,其二是AdvisedSupport
管理的通知資訊。通用的配置資訊我們可以直接在XML中配置,例如在上面的例子中我們就配置了proxyTargetClass
屬性,而通知資訊即使我們在XML中配置了也還需要做一層轉換,在前面我們也提到過了,所有的Advice
都會被轉換成Advisor
新增到配置資訊中。
ProxyFactory的方式建立AOP代理
使用示例(略,見開頭)
ProxyFactory介紹
從上面我們可以看出,ProxyFactory
也繼承自ProxyCreatorSupport
,從之前的例子我們也能感受到,使用它的API來建立一個代理物件也是要先去設定相關的配置資訊,最後再呼叫建立代理的方法
我們之後要分析的自動代理
內部就是通過建立了一個ProxyFactory
來獲取代理物件的。
我們可以對比下ProxyFactoryBean
跟ProxyFactory
在建立代理物件時的程式碼
ProxyFactory
public Object getProxy() {
// 呼叫了ProxyCreatorSupport的createAopProxy()方法建立一個AopProxy物件
// 然後呼叫AopProxy物件的getProxy方法
return createAopProxy().getProxy();
}
ProxyFactoryBean
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
Class<?> targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
super.setFrozen(this.freezeProxy);
// 重點就看這裡
// 這裡呼叫了ProxyCreatorSupport的createAopProxy()方法建立一個AopProxy物件
// 而getProxy方法就是呼叫建立的AopProxy的getProxy方法
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
protected Object getProxy(AopProxy aopProxy) {
return aopProxy.getProxy(this.proxyClassLoader);
}
綜上,我們可以得出結論,不管是通過哪種方式建立AOP代理,核心程式碼就一句
createAopProxy().getProxy()
這句程式碼也是我們接下來原始碼分析的重點
Auto-proxy(實現自動AOP代理)
自動代理機制的實現其實很簡單,就是通過Bean的後置處理器,在建立Bean的最後一步對Bean進行代理,並將代理物件放入到容器中。
實現自動代理的核心類就是AbstractAutoProxyCreator
。我們來看看它的繼承關係
為了更好的體會自動代理的作用,我們對它的三個具體的實現類來進行分析,分別是
BeanNameAutoProxyCreator
DefaultAdvisorAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator
BeanNameAutoProxyCreator
使用示例
<?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.xsd">
<bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>
<bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>
<bean id="beforeAdvice" class="com.dmz.spring.initalize.aop.advice.DmzBeforeAdvice"/>
<!--使用很簡單,只要配置一個BeanNameAutoProxyCreator即可-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" name="autoProxyCreator">
<!--使用cglib代理-->
<property name="proxyTargetClass" value="true"/>
<!--對所有以dmz開頭的bean進行自動代理-->
<property name="beanNames" value="dmz*"/>
<!--新增兩個通知-->
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>aroundAdvice</value>
</list>
</property>
</bean>
</beans>
public class SourceMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application-init.xml");
DmzService dmzProxy = ((DmzService) cc.getBean("dmzService"));
dmzProxy.testAop();
}
}
// 程式列印:
// before invoke method [testAop],aop before logic invoked
// aroundAdvice invoked
// testAop invoke
DefaultAdvisorAutoProxyCreator
使用示例
在上面例子的基礎上我們要修改配置檔案,如下:
<?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.xsd">
<bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/>
<bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/>
<bean id="beforeAdvice" class="com.dmz.spring.initalize.aop.advice.DmzBeforeAdvice"/>
<bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dmzBeforeAdvisor">
<property name="advice" ref="beforeAdvice"/>
</bean>
<bean class="org.springframework.aop.support.DefaultPointcutAdvisor" id="dmzAroundAdvisor">
<property name="advice" ref="aroundAdvice"/>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
id="advisorAutoProxyCreator">
<!--這兩個引數標明瞭我們要使用所有以dmz開頭的Advisor型別的通知
這裡必須配置是Advisor,不能是Advice或者interceptor,
可以看到DefaultAdvisorAutoProxyCreator跟BeanNameAutoProxyCreator的區別在於
BeanNameAutoProxyCreator需要指定要被代理的bean的名稱,
而DefaultAdvisorAutoProxyCreator不需要,它會根據我們傳入的Advisor
獲取到需要被代理的切點
-->
<property name="usePrefix" value="true"/>
<property name="advisorBeanNamePrefix" value="dmz"/>
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
測試程式碼就不放了,大家可以自行測試,肯定是沒問題的
AnnotationAwareAspectJAutoProxyCreator
我們正常在使用AOP的時候都會在配置類上新增一個@EnableAspectJAutoProxy
註解,這個註解幹了什麼事呢?
實際就是向容器中註冊了一個AnnotationAwareAspectJAutoProxyCreator
。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 這裡匯入了一個類
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
通過@EnableAspectJAutoProxy
匯入了一個AspectJAutoProxyRegistrar
,這個類會向容器中註冊一個AnnotationAwareAspectJAutoProxyCreator
,對應原始碼如下:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 在這裡完成的註冊
// 最終會呼叫到AopUtils的registerAspectJAnnotationAutoProxyCreatorIfNecessary方法
// 完成AnnotationAwareAspectJAutoProxyCreator這個bd的註冊
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// 解析註解的屬性
// proxyTargetClass:為true的話開啟cglib代理,預設為jdk代理
// exposeProxy:是否將代理物件暴露到執行緒上下文中
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
前面已經說過了,自動代理機制實際上就是Spring在內部new了一個ProxyFactory
,通過它建立了一個代理物件。對應的程式碼就在AbstractAutoProxyCreator
中的createProxy
方法內,原始碼如下:
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
// 看到了吧,這裡建立了一個proxyFactory
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 通過proxyFactory來建立一個代理物件
return proxyFactory.getProxy(getProxyClassLoader());
}
關於這個類的執行流程在下篇文章中我再詳細介紹,接下來我們要分析的就是具體建立AOP代理的原始碼了。對應的核心原始碼就是我們之前所提到的
createAopProxy().getProxy();
這行程式碼分為兩步,我們逐步分析
- 呼叫
AopProxyFactory
的createAopProxy()
方法獲取一個AopProxy
物件 - 呼叫
AopProxy
物件的getProxy()
方法
核心原始碼分析
createAopProxy方法分析
AopProxyFactory
在Spring中只有一個預設的實現類,就是DefaultAopProxyFactory
,它的對應的createAopProxy
的是實現程式碼如下:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
// 就是通過AOP相關的配置資訊來決定到底是使用cglib代理還是jdk代理
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 如果開啟了優化,或者ProxyTargetClass設定為true
// 或者沒有提供代理類需要實現的介面,那麼使用cglib代理
// 在前面分析引數的時候已經說過了
// 預設情況下Optimize都為false,也不建議設定為true,因為會進行一些侵入性的優化
// 除非你對cglib的優化非常瞭解,否則不建議開啟
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 需要注意的是,如果需要代理的類本身就是一個介面
// 或者需要被代理的類本身就是一個通過jdk動態代理生成的類
// 那麼不管如何設定都會使用jdk動態代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
// 否則都是jdk代理
else {
return new JdkDynamicAopProxy(config);
}
}
// 判斷是否提供代理類需要實現的介面
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
}
getProxy方法分析
從對createAopProxy方法的分析可以看到,我們要麼執行的是ObjenesisCglibAopProxy
中的getProxy
方法,要麼就是JdkDynamicAopProxy
的getProxy
方法,二者的區別在於一個是通過cglib的方式生成代理物件,而後者則是通過jdk的方式生成動態代理。
這裡我只分析一個JdkDynamicAopProxy
,首先我們來看看這個類的繼承關係
希望你之前已經閱讀過
可以看到這個類本身就是一個InvocationHandler
,這意味著當呼叫代理物件中的方法時,最終會呼叫到JdkDynamicAopProxy
的invoke
方法。
所以對於這個類我們起碼應該關注兩個方法
getProxy
方法invoke
方法
getProxy
方法原始碼如下:
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
// 這裡獲取到代理類需要實現的所有的介面
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
// 需要明確是否在介面定義了hashCode以及equals方法
// 如果介面中沒有定義,那麼在呼叫代理物件的equals方法的時候
// 如果兩個物件相等,那麼意味著它們的目標物件,通知以及實現的介面都相同
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
我們再來看看到底是怎麼獲取到需要實現的介面的
static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
// 第一步:獲取在配置中指定的需要實現的介面
Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
// 第二步:如果沒有指定需要實現的介面,但是需要代理的目標類本身就是一個介面
// 那麼將其新增到代理類需要實現的介面的集合中
// 如果目標類本身不是一個介面,但是是經過jdk代理後的一個類
// 那麼獲取這個代理後的類所有實現的介面,並新增到需要實現的介面集合中
if (specifiedInterfaces.length == 0) {
Class<?> targetClass = advised.getTargetClass();
if (targetClass != null) {
if (targetClass.isInterface()) {
advised.setInterfaces(targetClass);
}
else if (Proxy.isProxyClass(targetClass)) {
advised.setInterfaces(targetClass.getInterfaces());
}
specifiedInterfaces = advised.getProxiedInterfaces();
}
}
// 第三步:為代理類新增三個預設需要實現的介面,分別是
// 1.SpringProxy,一個標記介面,代表這個類是通過Spring的AOP代理生成的
// 2.Advised,提供了管理通知的方法
// 3.DecoratingProxy,使用者獲取到真實的目標物件
// 這個真實物件指的是在巢狀代理的情況下會獲取到最終的目標物件
// 而不是指返回這個ProxyFactory的target
boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));
int nonUserIfcCount = 0;
if (addSpringProxy) {
nonUserIfcCount++;
}
if (addAdvised) {
nonUserIfcCount++;
}
if (addDecoratingProxy) {
nonUserIfcCount++;
}
Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
int index = specifiedInterfaces.length;
if (addSpringProxy) {
proxiedInterfaces[index] = SpringProxy.class;
index++;
}
if (addAdvised) {
proxiedInterfaces[index] = Advised.class;
index++;
}
if (addDecoratingProxy) {
proxiedInterfaces[index] = DecoratingProxy.class;
}
return proxiedInterfaces;
}
invoke方法分析
在確認了需要實現的介面後,直接呼叫了jdk的動態代理方法,這個我們就不做分析了,接下來我們來看看Spring是如何將通知應用到代理物件上的,對應的要分析的程式碼就是JdkDynamicAopProxy
的invoke
方法,原始碼如下:
// 這個方法的程式碼稍微有點長,程式碼也比較難,希望大家能耐心看完
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// 首先處理的是hashCode跟equals方法
// 如果介面中沒有定義這兩個方法,那麼會呼叫本類中定義的equals方法
// 前面我們也說過了,只有當兩個類的目標物件,通知以及實現的介面都相等的情況下
// equals才會返回true
// 如果介面中定義了這兩個方法,那麼最終會呼叫目標物件中的方法
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
return hashCode();
}
// 也就是說我們呼叫的是DecoratingProxy這個介面中的方法
// 這個介面中只定義了一個getDecoratedClass方法,用於獲取到
// 最終的目標物件,在方法實現中會通過一個while迴圈來不斷接近
// 最終的目標物件,直到得到的目標物件不是一個被代理的物件才會返回
else if (method.getDeclaringClass() == DecoratingProxy.class) {
return AopProxyUtils.ultimateTargetClass(this.advised);
}
// 說明呼叫的是Advised介面中的方法,這裡只是單純的進行反射呼叫
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
// 說明需要將代理類暴露到執行緒上下文中
// 呼叫AopContext.setCurrentProxy方法將其放入到一個threadLocal中
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// 接下來就是真正的執行代理邏輯了
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 先獲取整個攔截器鏈
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 如果沒有進行攔截,直接反射呼叫方法
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
// 否則開始執行整個鏈條
else {
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
// 這裡是處理一種特殊情況,就是當執行的方法返回值為this的情況
// 這種情況下,需要返回當前的代理物件而不是目標物件
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
AopContext.setCurrentProxy(oldProxy);
}
}
}
在上面整個流程中,我們抓住核心的兩步
- 獲取整個攔截器鏈
- 開始在攔截器鏈上執行方法
我們先看第一步,對應原始碼如下:
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
// 呼叫了advisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class<?> targetClass) {
List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
// 是否有引入通知
boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
// 獲取到所有的通知
for (Advisor advisor : config.getAdvisors()) {
// 除了引入通知外,可以認為所有的通知都是一個PointcutAdvisor
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
// config.isPreFiltered:代表的是配置已經過濾好了,是可以直接應用的
// 這句程式碼的含義就是配置是預過濾的或者在類級別上是匹配的
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
// 接下來要判斷在方法級別上是否匹配
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
// 將通知轉換成對應的攔截器
// 有些通知本身就是攔截器,例如環繞通知
// 有些通知需要通過一個AdvisorAdapter來適配成對應的攔截器
// 例如前置通知,後置通知,異常通知等
// 其中MethodBeforeAdvice會被適配成MethodBeforeAdviceInterceptor
// AfterReturningAdvice會被適配成AfterReturningAdviceInterceptor
// ThrowAdvice會被適配成ThrowsAdviceInterceptor
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
// 如果是動態的攔截,會建立一個InterceptorAndDynamicMethodMatcher
// 動態的攔截意味著需要根據具體的引數來決定是否進行攔截
if (mm.isRuntime()) {
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
// 說明是引入通知
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
// 前文我們有提到過,引入通知實際就是通過一個攔截器
// 將方法交由引入的類執行而不是目標類
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
// 可能會擴充套件出一些通知,一般不會
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
return interceptorList;
}
在構建好攔截器鏈後,接下來就是真正執行方法了,對應程式碼就是
// 先建立一個MethodInvocation
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 開始在攔截器鏈上執行這個方法
retVal = invocation.proceed();
最後的關鍵程式碼就落在了ReflectiveMethodInvocation
的proceed
方法
public Object proceed() throws Throwable {
// 滿足這個條件,說明執行到了最後一個攔截器,那麼直接反射呼叫目標方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 獲取到下一個要執行的攔截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// 前面構建攔截器鏈的時候我們可以看到,動態的攔截的話會建立一個InterceptorAndDynamicMethodMatcher
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// 如果匹配失敗了,執行攔截器鏈中的下一個攔截邏輯
return proceed();
}
}
else {
// 呼叫攔截器中的invoke方法,可以看到這裡將this作為引數傳入了
// 所以我們在攔截器中呼叫 MethodInvocation的proceed時又會進行入當前這個方法
// 然後去執行鏈條中的下一個攔截器
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
總結
本文主要是為下篇文章做準備,下篇文章將會結束整個IOC流程的分析,IOC的最後一步便是為Bean建立代理。本文已經分析了代理的具體建立邏輯,在下篇文章中我們主要結合Spring的啟動流程來看一看Spring是如何將通知新增到建立代理的配置資訊中去的。
關於整個IOC跟AOP的模組還會有兩篇文章,一篇用於結束整個IOC流程,另外一篇專門探討Spring中迴圈依賴的解決。完成這兩篇文章中,接下來打算用5到7篇文章對Spring的事務管理進行分析!
如果我的文章能幫到你,記得點個贊哈~!
如果本文對你有幫助的話,記得點個贊吧!也歡迎關注我的公眾號,微信搜尋:程式設計師DMZ,或者掃描下方二維碼,跟著我一起認認真真學Java,踏踏實實做一個coder。
我叫DMZ,一個在學習路上匍匐前行的小菜鳥!