Spring AOP 增強框架 Nepxion Matrix 詳解
概述
本篇來繼續介紹一款開源的 AOP 框架:Nepxion Matrix
,該框架致力於對 Spring AOP 的擴充套件和增強,靈活而且易用。
Matrix 框架主要對 Spring 做了三個模組的擴充套件:Spring AutoProxy,Spring Registrar,Spring Selectror。
本篇主要分析 AOP相關的功能,也就是 AutoProxy 模組。主要圍繞以下幾個方面:
-
Nepxion Matrix AutoProxy
框架有什麼特性? -
Nepxion Matrix AutoProxy
AOP 增強框架的擴充套件點什麼?如何擴充套件? -
原始碼分析。
-
該框架和
Spring AOP
異同點。
一:Nepxion Matrix AutoProxy特性
大多數的專案中,使用Spring AOP
的方式都是採用註解形式,方法上面加個自定義註解即可實現,這種方式註解只能加在類方法上,不能加在介面或介面方法上。Nepxion Matrix AutoProxy
主要針對這些問題,特性如下:
-
支援通用代理和額外代理
-
支援介面代理和類代理
-
支援介面方法代理
這裡要介紹一下上面提到了兩種代理方式:
通用代理是指通過AbstractAutoProxyCreator
中的變數interceptorNames
二:Nepxion Matrix AutoProxy擴充套件點
要理解該框架的實現方式,首先要知道該框架的擴充套件點是什麼。
先來看一下代理機制相關的 UML 圖:
AbstractAutoProxyCreator
抽象類為Spring AOP
暴露的抽象擴充套件類,其每一個實現類都代表著一種實現機制。Nepxion Matrix
也正是基於此類做為擴充套件點,分別來看一下涉及到核心類:
-
AbstractAutoScanProxy
Nepxion Matrix
提供的核心抽象類,封裝了獲取顧問advisor
的方法,並暴露了一些抽象方法,如獲取通知,註解等方法。該類同 Spring 內建的代理機制AbstractAdvisorAutoProxyCreator
平級,預設先執行Spring AOP
內建代理機制。 -
DefaultAutoScanProxy
:提供了一些預設為空的實現,不能直接使用。 -
MyAutoScanProxyForClass
:類代理機制,提供通用代理實現。 -
MyAutoScanProxyForMethod
:方法代理機制,提供額外代理。 -
MyAutoScanProxy
:混合代理,提供通用代理和額外代理。
三:原始碼分析
這裡就針對類代理的方式,進行原始碼分析。先來看原始碼中的使用示例:
@MyAnnotation1(name = "MyAnnotation1", label = "MyAnnotation1", description = "MyAnnotation1")
public interface MyService1 {
void doA(String id);
void doB(String id);
}
@Service
public class MyService1Impl implements MyService1 {
@Override
public void doA(String id) {
System.out.println("doA");
}
@Override
public void doB(String id) {
System.out.println("doB");
}
}
示例中只需在介面上新增一個自定義註解@MyAnnotation1
,即可滿足兩個實現方法都會走代理方法。
原始碼中還有其他幾種使用示例,這裡就不列舉了,具體可以參考專案wiki。
首先來看一下AbstractAutoScanProxy
的構造方法:
public AbstractAutoScanProxy(String[] scanPackages, ProxyMode proxyMode, ScanMode scanMode, boolean exposeProxy) {
//設定代理目錄,非指定目錄直接返回
this.scanPackages = scanPackages;
//Spring提供的是否暴露代理物件標識。
this.setExposeProxy(exposeProxy);
//代理模式,類代理或是方法代理。
this.proxyMode = proxyMode;
this.scanMode = scanMode;
//......
// 設定全域性攔截器,通過名稱指定。
// 如果同時設定了全域性和額外的攔截器,那麼它們都同時工作,全域性攔截器先執行,額外攔截器後執行
Class<? extends MethodInterceptor>[] commonInterceptorClasses = getCommonInterceptors();
String[] commonInterceptorNames = getCommonInterceptorNames();
String[] interceptorNames = ArrayUtils.addAll(commonInterceptorNames, convertInterceptorNames(commonInterceptorClasses));
if (ArrayUtils.isNotEmpty(interceptorNames)) {
setInterceptorNames(interceptorNames);
}
}
構造方法中有兩個變數比較重要:exposeProxy
:預設為false,這裡設定為 true,支援在同一個類中,一個方法呼叫另一個方法走代理攔截方法。 比如,類中方法1呼叫方法2,開啟該變數,則不會直接呼叫方法2,而是從 threadLocal 中取出提前存入的代理類發起呼叫。
interceptorNames
:通知名稱,也就是通用代理,通過構造方法設定。在後面生成代理類的方法中會根據該變數取出所有攔截器例項。
我們來看一下代理執行入口。因為該類繼承beanPostProcessor
,所以最終會執行擴充套件介面postProcessAfterInitialization
,在該方法中呼叫模板方法getAdvicesAndAdvisorsForBean
,來看一下Nepxion Matrix
對該方法的實現:
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
boolean scanPackagesEnabled = scanPackagesEnabled();
// scanPackagesEnabled=false,表示“只掃描指定目錄”的方式未開啟,則不會對掃描到的bean進行代理預先判斷
if (scanPackagesEnabled) {
boolean scanPackagesContained = scanPackagesContained(beanClass);
// 如果beanClass的類路徑,未包含在掃描目錄中,返回DO_NOT_PROXY
if (!scanPackagesContained) {
return DO_NOT_PROXY;
}
}
// 根據Bean名稱獲取Bean物件
Object bean = beanMap.get(beanName);
// 獲取最終目標類,
Class<?> targetClass = null;
if (bean != null /* && AopUtils.isCglibProxy(bean) */) {
targetClass = AopProxyUtils.ultimateTargetClass(bean);
} else {
targetClass = beanClass;
}
// Spring容器掃描實現類
if (!targetClass.isInterface()) {
// 掃描介面(從實現類找到它的所有介面)
if (targetClass.getInterfaces() != null) {
for (Class<?> targetInterface : targetClass.getInterfaces()) {
Object[] proxyInterceptors = scanAndProxyForTarget(targetInterface, beanName, false);
if (proxyInterceptors != DO_NOT_PROXY) {
return proxyInterceptors;
}
}
}
// 掃描實現類(如果介面上沒找到註解, 就找實現類的註解)
Object[] proxyInterceptors = scanAndProxyForTarget(targetClass, beanName, true);
if (proxyInterceptors != DO_NOT_PROXY) {
return proxyInterceptors;
}
}
return DO_NOT_PROXY;
}
上面邏輯中呼叫了AopProxyUtils.ultimateTargetClass(bean)
來獲取對應的 class 物件,而不是使用引數中的beanClass
。因為方法傳進來的 class 物件有可能是被代理過的 class,所以這裡要獲取最初的 class 物件。
推薦一個Java進階架構學習交流:952124565,群內有分散式架構、高效能、高併發、效能優化、Spring boot、Redis、ActiveMQ、Nginx、Netty、Jvm等視訊資料提供學習參考。
繼續跟進scanAndProxyForTarget
方法:
protected Object[] scanAndProxyForTarget(Class<?> targetClass, String beanName, boolean proxyTargetClass) {
String targetClassName = targetClass.getCanonicalName();
//這裡獲取額外代理
Object[] interceptors = getInterceptors(targetClass);
// 排除java開頭的介面,例如java.io.Serializable,java.io.Closeable等,執行不被代理
if (StringUtils.isNotEmpty(targetClassName) && !targetClassName.startsWith("java.")) {
// 避免對同一個介面或者類掃描多次
Boolean proxied = proxyMap.get(targetClassName);
if (proxied != null) {
if (proxied) {
return interceptors;
}
} else {
Object[] proxyInterceptors = null;
switch (proxyMode) {
// 只通過掃描到介面名或者類名上的註解後,來確定是否要代理
case BY_CLASS_ANNOTATION_ONLY:
proxyInterceptors = scanAndProxyForClass(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
break;
// 只通過掃描到介面或者類方法上的註解後,來確定是否要代理
case BY_METHOD_ANNOTATION_ONLY:
proxyInterceptors = scanAndProxyForMethod(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
break;
// 上述兩者都可以
case BY_CLASS_OR_METHOD_ANNOTATION:
Object[] classProxyInterceptors = scanAndProxyForClass(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
// 沒有介面或者類名上掃描到目標註解,那麼掃描介面或者類的方法上的目標註解
Object[] methodProxyInterceptors = scanAndProxyForMethod(targetClass, targetClassName, beanName, interceptors, proxyTargetClass);
if (classProxyInterceptors != DO_NOT_PROXY || methodProxyInterceptors != DO_NOT_PROXY) {
proxyInterceptors = interceptors;
} else {
proxyInterceptors = DO_NOT_PROXY;
}
break;
}
// 是否需要代理
proxyMap.put(targetClassName, Boolean.valueOf(proxyInterceptors != DO_NOT_PROXY));
return proxyInterceptors;
}
}
return DO_NOT_PROXY;
}
大致的思路:根據MyService1Impl
獲取到介面MyService1
,然後判斷介面上是否有指定的註解@MyAnnotation1
,判斷條件符合,然後呼叫getInterceptors
方法獲取攔截器,傳遞到父類AbstractAutoProxyCreator
中的方法createProxy
中,完成代理。
跟進getInterceptors
方法來看一下:
protected Object[] getInterceptors(Class<?> targetClass) {
Object[] interceptors = getAdditionalInterceptors(targetClass);
if (ArrayUtils.isNotEmpty(interceptors)) {
return interceptors;
}
Class<? extends MethodInterceptor>[] commonInterceptorClasses = getCommonInterceptors();
String[] commonInterceptorNames = getCommonInterceptorNames();
if (ArrayUtils.isNotEmpty(commonInterceptorClasses) || ArrayUtils.isNotEmpty(commonInterceptorNames)) {
return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
}
return DO_NOT_PROXY;
}
這裡先獲取所有的額外代理攔截器,如果有直接返回。如果為空,則返回一個是否有通用代理攔截器的標識,具體攔截器的名稱上面已經通過構造方法傳入。
再來看一下在createProxy
方法:
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
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);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
Nepxion Matrix
重寫了上面的shouldProxyTargetClass(beanClass, beanName)
方法,重寫邏輯如下:
protected boolean shouldProxyTargetClass(Class<?> beanClass, String beanName) {
// 設定不同場景下的介面代理,還是類代理
Boolean proxyTargetClass = proxyTargetClassMap.get(beanName);
if (proxyTargetClass != null) {
return proxyTargetClass;
}
return super.shouldProxyTargetClass(beanClass, beanName);
}
需要注意的是,上述重寫方式只在SpringBoot 1.x
中生效,因為在 2.x
版本中,proxyFactory.isProxyTargetClass()
預設為 true,預設走 cglib 代理,所以預設情況下上述重寫的方法不會執行。
繼續跟進獲取攔截器的方法buildAdvisors
:
protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
//解析通用代理攔截器
Advisor[] commonInterceptors = resolveInterceptorNames();
List<Object> allInterceptors = new ArrayList<Object>();
//判斷是否需要額外代理,是否有指定攔截器
if (specificInterceptors != null) {
allInterceptors.addAll(Arrays.asList(specificInterceptors));
if (commonInterceptors.length > 0) {
if (this.applyCommonInterceptorsFirst) {
allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
}
else {
allInterceptors.addAll(Arrays.asList(commonInterceptors));
}
}
}
Advisor[] advisors = new Advisor[allInterceptors.size()];
for (int i = 0; i < allInterceptors.size(); i++) {
advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
}
return advisors;
}
通過呼叫resolveInterceptorNames
,根據interceptorNames
中設定的攔截器名稱,從Spring
容器中取出所有的通用代理攔截器,結合指定攔截器specificInterceptors
,一起織入代理類。
Nepxion Matrix AutoProxy
中的方法代理這裡就不展開了,原理類似。
四:Nepxion Matrix AutoProxy 和 Spring AOP 異同點
1)代理機制原理一樣,都是AbstractAutoScanProxy
的實現類,只是代理功能點不同。
2)兩種代理機制可同時使用。如果同時使用,一定保證Spring AOP
先代理,Nepxion Matrix AutoProxy
後代理。這也是預設的代理順序。儘量不要通過重寫Ordered
介面的方式改變先後順序。
原因是採用Spring AOP
註解形式時需要獲取代理類最初的 Class 物件,如果Nepxion Matrix AutoProxy
先執行,那麼在執行Spring AOP
代理邏輯時獲取到的當前 Class 物件就是被代理過重新生成的 Class 物件,這時就無法獲取自定義的切面註解了。
歡迎Java工程師朋友們加入Java進階架構學習交流:952124565
本群提供免費的學習指導 架構資料 以及解答,不懂得問題都可以在本群提出來 之後還會有職業生涯規劃以及面試指導。