推薦工具 objectlog
對于重要的一些數據,我們需要記錄一條記錄的所有版本變化過程,做到持續追蹤,為后續問題追蹤提供思路。objectlog工具是一個記錄單個對象屬性變化的日志工具,工具采用spring切面和mybatis攔截器相關技術編寫了api依賴包,以非侵入方式實現對標記的對象屬性進行記錄,僅需要導入依賴即可,幾乎不需要對原系統代碼改動,下面展示簡單的效果(根據對象field渲染即可):
該系統具有以下特點:
- 簡單易用:系統將核心邏輯抽離,采用非侵入方式,只需要導入依賴后標注相關注解即可。
- 業務共享:系統可以同時供多個業務系統使用,彼此之間互不影響。
- 自動解析:能自動解析對象的屬性變化,自動生成變化記錄。
- 便于擴展:支持更多對象屬性類型的擴展,支持自定義解析處理邏輯等。
- 工具性能:工具采用線程模式,脫離業務主線程,避免了解析過程對業務性能的影響。
開源地址:https://gitee.com/opensofte/objectlog,有興趣的朋友可以看看點個star.
使用背景
我們現在有一個業務場景,每次獲取數據的時候需要判斷有沒有權限數據,我們首先以oop角度來說明:
public class BusinessA {public void do(){//1、判斷權限//1.1 獲取當前方法訪問路徑String curPerm = getCurPerm();//1.2 獲取當前用戶的權限List permList = getPermByUserId(curUserId);//1.3 判斷當前用戶是否有權限if (!permList.contains(curPerm)) {//返回錯誤信息}//2、執行業務邏輯}
}
現在假如業務B也需要進行權限驗證,那么聰明的你一定想到了拷貝一份就好啦!
public class BusinessB {public void do() {//1、判斷權限//1.1 獲取當前方法訪問路徑String curPerm = getCurPerm();//1.2 獲取當前用戶的權限List permList = getPermByUserId(curUserId);//1.3 判斷當前用戶是否有權限if (!permList.contains(curPerm)) {//返回錯誤信息}//2、執行業務邏輯}
}
此時聰明的你又發現,兩邊代碼一樣,可以提出一個公共方法來處理:
public class BusinessA {public void do(){if (!Util.hasPerm()) {//返回錯誤信息}//2、執行業務邏輯}
}
public class BusinessB {public void do(){if (!Util.hasPerm()) {//返回錯誤信息}//2、執行業務邏輯}
}
public class Util {//1、判斷權限public static boolean hasPerm(){ //1.1 獲取當前方法訪問路徑String curPerm = getCurPerm();//1.2 獲取當前用戶的權限List permList = getPermByUserId(Context.currentUserId());//Context中封裝了用戶信息,提供給上下文使用//1.3 判斷當前用戶是否有權限return permList.contains(curPerm);}
}
到這里,我們就完成了這次業務的邏輯。過了一段時間你的leader告訴你,有的接口如果標注了@IsNotPerm這個注解,就不需要進行權限驗證了。聰明的你馬上就想到了在Util方法中加一個參數,改改調用的地方就好了。但此時看著100+的調用鏈路,你陷入了沉思,為什么當初不說還有這些改動。此時你有些絕望的打開百度,經過一番搜索你發現可以用aop來解決這個問題。
到這里你知道為什么用spring的AOP而不是創建一個工具類來解決呢?
于是你馬上寫了一個切面信息,然后把權限判斷邏輯放入其中,
@Aspect
@Component
@Slf4j
public class LogAspect {private final Logger logger = LoggerFactory.getLogger(LogClient.class);@Pointcut("@annotation(org.sweetie.objectlog.core.annotation.PermValidate)")public void objectLogPointCut() {}@Around(value = "objectLogPointCut()")public <T extends BaseEntity> void saveObject(ProceedingJoinPoint joinPoint) throws Throwable {//獲取切入點信息MethodSignature sign = (MethodSignature) joinPoint.getSignature();Method method = sign.getMethod();// 判斷有沒有IsNotPerm標記IsNotPerm annotation = method.getAnnotation(IsNotPerm.class);boolean allowDoBusiness = annotation != null;// 如果切入點需要校驗權限if (!allowDoBusiness) {//1.1 獲取當前方法訪問路徑String curPerm = getCurPerm();//1.2 獲取當前用戶的權限List permList = getPermByUserId(Context.currentUserId());//Context中封裝了用戶信息,提供給上下文使用//1.3 判斷當前用戶是否有權限allowDoBusiness = permList.contains(curPerm);}if(allowDoBusiness) {//執行業務邏輯joinPoint.proceed(joinPoint.getArgs());} else {//拋出異常給controller捕捉}}
}
此時你將業務中的方法進行了改造
public class BusinessA {@PermValidatepublic void do(){//2、執行業務邏輯}
}
public class BusinessB {@PermValidatepublic void do(){//2、執行業務邏輯}
}
此時你發現,業務中調用權限校驗的地方都消失了,業務邏輯仿佛一下子輕盈了起來
可以看到aop對業務代碼進行了解耦,也方便擴展
。
Aop思想介紹
Aop的定義
AOP (Aspect Orient Programming),直譯過來就是面向切面編程,AOP是一種編程思想,是面向對象編程(OOP)的一種補充。通過上面的例子我們可以發現面向切面編程,能夠在不修改源代碼的情況下給程序動態統一添加額外功能的一種技術。
AOP可以攔截指定的方法并且對方法增強,而且無需侵入到業務代碼中,使業務與非業務處理邏輯分離,比如Spring的事務,通過事務的注解配置,Spring會自動在業務方法中開啟、提交業務,并且在業務處理失敗時,執行相應的回滾策略。
一文了解spring事務特性
常見的Aop術語
名稱 | 說明 |
---|---|
Joinpoint(連接點) | 指那些被攔截到的點,在Spring中,指可以被動態代理攔截目標類的為方法。 |
Pointcut(切入點) | 指要對哪些Joinpoint進行攔截,即被攔截的連接點。 |
Advice(通知) | 指攔截到Joinpoint之后要做的事情人即對切入點增強的內容。 |
Target(目標) | 指代理的目標對象。 |
Weaving(植入) | 指把增強代碼應用到目標上,生成代理對象的過程。 |
Proxy(代理) | 指生成的代理對象。 |
Aspect(切面) | 切入點和通知的結合。 |
常見的Aop織入時機
時期 | 說明 |
---|---|
編譯期 | 切面在目標類編譯時被織入,這種方式需要特殊的編譯器,Aspectj的織入編譯器就是以這種方式織入切面的 |
類加載期 | 切面在目標類加載到JVM時被織入,這種方式需要特殊的類加載我器(ClassLoader),它可以在目標類引入應用之前增強目標類的字節碼。 |
運行期 | 切面在應用運行的某個時期被織入一般情況下,在織入切面時,AOP容器會為目標對象動態創建一個代理對象,SpringAOP默認采用的就是這種織入方式 。 |
常見的Aop的應用場景
- 日志記錄
- 事務管理
- 權限驗證
- 性能監測
AOP可以攔截指定的方法,并且對方法增強,比如:事務、日志、權限、性能監測等增強,而且無需侵入到業務代碼中,使業務與非業務處理邏輯分離。
SpringAop
Spring AOP就是一款AOP的一種實現,Spring AOP 采用了兩種混合的實現方式:JDK 動態代理和 CGLib 動態代理。
- JDK動態代理:Spring AOP的首選方法。 每當目標對象實現一個接口時,就會使用JDK動態代理。目標對象必須實現接口
- CGLIB代理:如果目標對象沒有實現接口,則可以使用CGLIB代理。
下面介紹springAop中常見的通知類型。
Spring常見通知類型
通知 | 說明 |
---|---|
before(前置通知) | 通知方法在目標方法調用之前執行 |
after(后置通知) | 通知方法在目標方法返回或異常后調用 |
after-returning(返回后通知) | 通知方法會在目標方法返回后調用 |
after-throwing(拋出異常通知) | 通知方法會在目標方法拋出異常后調用 |
around(環繞通知) | 通知方法會將目標方法封裝起來,可以實現上述幾種通知 |
下面是通知執行的順序:
Spring切入點表達式
表達式類型 | 描述 |
---|---|
execution | 匹配方法切入點 |
within | 匹配指定類型 |
this | 匹配代理對象實例的類型 |
target | 匹配目標對象實例的類型 |
args | 匹配方法參數 |
bean | 匹配bean的id或名稱 |
@within | 匹配類型是否含有注解 |
@target | 匹配目標對象實例的類型是否含有注解 |
@annotation | 匹配方法是否含有注解 |
@args | 匹配方法參數類型是否含有注解 |
execution
: 匹配方法切入點。根據表達式描述匹配方法,是最通用的表達式類型,可以匹配方法、類、包。
注意也可以匹配拋出異常類型,省略時匹配任意類型
// 匹配public方法
execution(public * *(..))// 匹配名稱以set開頭的方法
execution(* set*(..))// 匹配AccountService接口或類的方法
execution(* com.xyz.service.AccountService.*(..))// 匹配service包及其子包的類或接口
execution(* com.xyz.service..*(..))
within
: 匹配指定類型。匹配指定類的任意方法,不能匹配接口。
// 匹配service包的類
within(com.xyz.service.*)// 匹配service包及其子包的類
within(com.xyz.service..*)// 匹配AccountServiceImpl類
within(com.xyz.service.AccountServiceImpl)
this
: 匹配代理對象實例的類型,匹配在運行時對象的類型。
// 匹配代理對象類型為service包下的類
this(com.xyz.service.*)// 匹配代理對象類型為service包及其子包下的類
this(com.xyz.service..*)// 匹配代理對象類型為AccountServiceImpl的類
this(com.xyz.service.AccountServiceImpl)
注意:基于 JDK 動態代理實現的 AOP,this 不能匹配接口的實現類,因為代理類和實現類并不是同一種類型,參閱《Spring中的AOP和動態代理》
target
: 匹配目標對象實例的類型,匹配 AOP 被代理對象的類型。
// 匹配目標對象類型為service包下的類
target(com.xyz.service.*)// 匹配目標對象類型為service包及其子包下的類
target(com.xyz.service..*)// 匹配目標對象類型為AccountServiceImpl的類
target(com.xyz.service.AccountServiceImpl)
args
: 匹配方法參數類型和數量,參數類型可以為指定類型及其子類。
// 匹配參數只有一個且為Serializable類型(或實現Serializable接口的類)
args(java.io.Serializable)// 匹配參數個數至少有一個且為第一個為Example類型(或實現Example接口的類)
args(cn.codeartist.spring.aop.pointcut.Example,..)
bean
: 通過 bean 的 id 或名稱匹配,支持*
通配符。
// 匹配名稱以Service結尾的bean
bean(*Service)// 匹配名稱為demoServiceImpl的bean
bean(demoServiceImpl)
@within
: 匹配指定類型是否含有注解。當定義類時使用了注解,該類的方法會被匹配,但在接口上使用注解不匹配。
// 匹配使用了Demo注解的類
@within(cn.codeartist.spring.aop.pointcut.Demo)
@target
: 匹配目標對象實例的類型是否含有注解。當運行時對象實例的類型使用了注解,該類的方法會被匹配,在接口上使用注解不匹配。
// 匹配對象實例使用了Demo注解的類
@target(cn.codeartist.spring.aop.pointcut.Demo)
@annotation
: 匹配方法是否含有注解。當方法上使用了注解,該方法會被匹配,在接口方法上使用注解不匹配。
// 匹配使用了Demo注解的方法
@annotation(cn.codeartist.spring.aop.pointcut.Demo)
@args
: 匹配方法參數類型是否含有注解。當方法的參數類型上使用了注解,該方法會被匹配。
// 匹配參數只有一個且參數類使用了Demo注解
@args(cn.codeartist.spring.aop.pointcut.Demo)// 匹配參數個數至少有一個且為第一個參數類使用了Demo注解
@args(cn.codeartist.spring.aop.pointcut.Demo,..)
切點表達式的參數匹配:
切點表達式中的參數類型,可以和通知方法的參數通過名稱綁定,表達式中不需要寫類或注解的全路徑,而且能直接獲取到切面攔截的參數或注解信息。
- @Before(“pointcut() && args(name,…)”)
public void doBefore(String name) {
… // 切點表達式增加參數匹配,可以獲取到name的信息
}- @Before(“@annotation(demo)”)
public void doBefore(Demo demo) {
// 這里可以直接獲取到Demo注解的信息
}
切點表達式的參數匹配同樣適用于 @within, @target, @args
- 使用
&&、|| 和 ! 來組合多個切點表達式
,表示多個表達式“與”、“或”和“非”的邏輯關系。這可以用來組合多種類型的表達式,來提升匹配效率。匹配doExecution()切點表達式并且參數第一個為Account類型的方法:
@Before(“doExecution() && args(account,…)”)
public void validateAccount(Account account) {
// 自定義邏輯
}
springAop實現
注解和xml開啟aop支持方式如下:
//開啟配置文件支持
<!--配置Aspectj的自動代理-->
<aop:aspectj-autoproxy/>//開啟注解支持
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}
基于接口實現
配置通知時需實現org.springframework.aop包下的一些接口:
前置通知:MethodBeforeAdvice
后置通知:AfterReturningAdvice
環繞通知:MethodInterceptor
異常通知:ThrowsAdvice
前置通知攔截器MethodBeforeAdviceInterceptor:
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;}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {//前置處理 這個就是利用反射執行我們定義的前置方法this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());// 調用鏈條return mi.proceed();}
}
后置通知攔截器AfterReturningAdviceInterceptor:
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {private final AfterReturningAdvice advice;public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {Assert.notNull(advice, "Advice must not be null");this.advice = advice;}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {//先執行鏈條Object retVal = mi.proceed();// 后利用反射執行我們定義的后置通知方法this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());return retVal;}
}
異常通知攔截器ThrowsAdviceInterceptor :
public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {// 省略............@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {try {// 這個就是鏈條return mi.proceed();} catch (Throwable var4) {// 鏈條報錯了 就異常處理(還需要判斷是不是需要處理的異常)// 異常通知可以指定需要處理的異常Method handlerMethod = this.getExceptionHandler(var4);if (handlerMethod != null) {this.invokeHandlerMethod(mi, var4, handlerMethod);}throw var4;}}// 省略...............
}
最終通知AspectJAfterAdvice :
public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable {public AspectJAfterAdvice(Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {super(aspectJBeforeAdviceMethod, pointcut, aif);}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {Object var2;try {// 先執行鏈條var2 = mi.proceed();} finally {//最終執行this.invokeAdviceMethod(this.getJoinPointMatch(), (Object)null, (Throwable)null);}return var2;}}
環繞通知AspectJAroundAdvice :
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {public AspectJAroundAdvice(Method aspectJAroundAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {super(aspectJAroundAdviceMethod, pointcut, aif);}@Nullablepublic Object invoke(MethodInvocation mi) throws Throwable {if (!(mi instanceof ProxyMethodInvocation)) {throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);} else {ProxyMethodInvocation pmi = (ProxyMethodInvocation)mi;ProceedingJoinPoint pjp = this.lazyGetProceedingJoinPoint(pmi);JoinPointMatch jpm = this.getJoinPointMatch(pmi);// 這個就是去執行我們 自己寫的環繞通知方法 // 所以環繞通知方法一定會有個參數嘛 joinPoint.proceed()就是執行鏈條return this.invokeAdviceMethod(pjp, jpm, (Object)null, (Throwable)null);}}protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) {return new MethodInvocationProceedingJoinPoint(rmi);}
}
以上就是關于通知鏈條里面所有最后會執行的方法,可以看到共同點就是invoke方法的傳參MethodInvocation ,這不就是我們之前說的連接點嘛,當然還有很多內置的其他攔截器,但這都跟我們AOP攔截器沒關系
基于XML配置實現
public class LogAspectj {//前置通知public void beforeAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj前置通知】 ==========");}//后置通知:方法正常執行后,有返回值,執行該后置通知:如果該方法執行出現異常,則不執行該后置通知 public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){System.out.println("========== 【Aspectj后置通知】 ==========");}public void afterAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj后置通知】 ==========");}//環繞通知public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【環繞通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【環繞通知中的后置通知】##########");return returnVale;}/*** 異常通知:方法出現異常時,執行該通知*/public void throwAdvice(JoinPoint joinPoint, Exception ex){System.out.println("出現異常:" + ex.getMessage());}}
<!--開啟aop支持,注意需要引入aop標簽-->
<aop:aspectj-autoproxy/><!--業務組件bean, 需要使用到切面的bean-->
<bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/><!--日志Aspect切面-->
<bean id="logAspectjBean" class="com.apesource.log.LogAspectj"/><!--使用Aspectj實現切面,使用Spring AOP進行配置-->
<aop:config><!--配置切面--><!--注入切面bean--><aop:aspect ref="logAspectjBean"><!--定義Pointcut:通過expression表達式,來查找 特定的方法(pointcut)--><aop:pointcut id="pointcut"expression="execution(* com.apesource.service.impl.*.create*(..))"/><!--配置"前置通知"--><!--在pointcut切入點(serviceMethodPointcut)查找到 的方法執行"前",來執行當前logAspectBean的doBefore--><aop:before method="beforeAdvice" pointcut-ref="pointcut"/><!--配置“后置通知”--><!--returning屬性:配置當前方法中用來接收返回值的參數名--><aop:after-returning returning="returnVal" method="afterReturningAdvice" pointcut-ref="pointcut"/> <aop:after method="afterAdvice" pointcut-ref="pointcut"/><!--配置"環繞通知"--><aop:around method="aroundAdvice" pointcut-ref="pointcut"/><!--配置“異常通知”--><!--throwing屬性:配置當前方法中用來接收當前異常的參數名--><aop:after-throwing throwing="ex" method="throwAdvice" pointcut-ref="pointcut"/></aop:aspect></aop:config>
基于注解的實現
//聲明當前類為Aspect切面,并交給Spring容器管理
@Component
@Aspect
public class LogAnnotationAspectj {private final static String EXPRESSION = "execution(* com.apesource.service.impl.*.create*(..))";//前置通知 @Before(EXPRESSION)public void beforeAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj前置通知】 ==========");}//后置通知:方法正常執行后,有返回值,執行該后置通知:如果該方法執行出現異常,則不執行該后置通知@AfterReturning(value = EXPRESSION,returning = "returnVal")public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){System.out.println("========== 【Aspectj后置通知】 ==========");}//后置通知@After(EXPRESSION)public void afterAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj后置通知】 ==========");}//環繞通知@Around(EXPRESSION)public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【環繞通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【環繞通知中的后置通知】##########");return returnVale;}// 異常通知:方法出現異常時,執行該通知@AfterThrowing(value = EXPRESSION,throwing = "ex")public void throwAdvice(JoinPoint joinPoint, Exception ex){System.out.println("********** 【Aspectj異常通知】執行開始 **********");System.out.println("出現異常:" + ex.getMessage());System.out.println("********** 【Aspectj異常通知】執行結束 **********");}}
SpringAop失效場景
避免 Spring 的 AOP 的自調用問題在 Spring 的 AOP 代理下,只能目標方法由外部調用。我們可以使用ltw來解決這個問題https://blog.csdn.net/c39660570/article/details/106791365
切面類未被注冊為bean。
SpringAop生效原理
aop切入點
從AOP配置加載點一看便知,開啟AOP的配置注解是 @EnableAspectJAutoProxy
在其內部導入了一個類AspectJAutoProxyRegistrar
<aop:aspectj-autoproxy/>
注意這個和上面一樣
AspectJAutoProxyRegistrar這個類實現了ImportBeanDefinitionRegistrar接口,這個接口之前說過了,可以注冊BeanDefination,所以我們要看看注冊的這個是什么?干了什么?
沿著那個方法一路往下,發現注冊了AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator這個類可謂是最重要的類了,從下方的類圖上看,它實現了很多接口,還有我們非常熟悉的后置處理器,在這里面主要實現了4個方法:
- setBeanFactory:實例化后,初始化前調用
- getEarlyBeanReference:和三級緩存有關,存在循環依賴里面會調用
- postProcessBeforeInstantiation:實例化前執行
- postProcessAfterInitialization:初始化后執行
別看有4個方法,其實下面三個方法內部都會調用一樣的方法,只是需要注意在Bean生成流程中的介入點
- AbstractAutoProxyCreator
實例前執行postProcessBeforeInstantiation()
實例前執行,主要是判斷代理目標對象是否已經存在了,存在了就走getAdvicesAndAdvisorsForBean方法,然后調用createProxy()方法創建代理對象
Object cacheKey = this.getCacheKey(beanClass, beanName);if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {if (this.advisedBeans.containsKey(cacheKey)) {return null;}if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return null;}}// 判斷代理目標對象是否已經存在了 存在了就進入代理流程TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);if (targetSource != null) {if (StringUtils.hasLength(beanName)) {this.targetSourcedBeans.add(beanName);}Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);// 創建動態代理對象Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;} else {return null;}
初始化后執行postProcessAfterInitialization
初始化后執行,會調用wrapIfNecessary()方法
//該bean初始化完畢之后,回調該方法判斷該bean是否需要被代理
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = this.getCacheKey(bean.getClass(), beanName);//如果該bean未執行過AOP,則進行封裝;如果執行過,則不再進行封裝if (this.earlyProxyReferences.remove(cacheKey) != bean) {return this.wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}
wrapIfNecessary()方法也會調用getAdvicesAndAdvisorsForBean
方法來獲取對應的通知處理,如果沒獲取到通知處理方法說明不需要代理,獲取到了就要創建代理對象了createProxy()
注意: 這里的通知處理就是切面里面的通知方法,getAdvicesAndAdvisorsForBean就是獲取所有的切面類里面的切點及通知方法與Bean來匹配,匹配上了說明這個Bean要被代理,同時會封裝匹配的切點對應的所有通知方法返回
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {// 獲取該bean的所有的通知處理Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);// 獲取的通知處理不為空 說明要代理if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 創建代理Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;} else {// 為空就不需要創建代理了 直接返回Beanthis.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}} else {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}
}
循環依賴會調用getEarlyBeanReference
三級緩存,存在循環依賴則會調用,這里put進去代表已經生成代理了,所以后續初始化后調用的時候會get判斷一次,這個也會調用wrapIfNecessary() 方法
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = this.getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);return this.wrapIfNecessary(bean, beanName, cacheKey);
}
總結
: 所以會在Bean實例化前、循環依賴、初始化后介入處理,當然只會處理一次,最終都會調用getAdvicesAndAdvisorsForBean方法來對Bean進行切點匹配,匹配上了就調用createProxy方法生成代理對象然后返回
獲取所有切面處理
AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean()
會先獲取所有的切面其下的通知方法,然后根據切點表達式去和這個Bean對象匹配,將匹配成功的通知方法返回,這就說明該Bean需要被代理,匹配成功的通知方法排序后就是需要執行的方法調用鏈
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {// 獲取所有切面其下的切面通知方法List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);// 為空返回空數組 不為空轉成數組返回return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
}// 獲取所有切面及其下的切面通知方法
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 獲取所有切面及其下的切面通知方法List<Advisor> candidateAdvisors = this.findCandidateAdvisors();// 從中根據切點篩選出符合Bean的通知方法List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);this.extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}
AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors
有個父類的方法是獲取一些實現了Advisor接口的Bean,我們重點關注被@Aspect注解標識的Bean的處理
protected List<Advisor> findCandidateAdvisors() {// 獲取所有實現了Advisor接口的Bean 有些內置的比如事務List<Advisor> advisors = super.findCandidateAdvisors();if (this.aspectJAdvisorsBuilder != null) {// 獲取被注解@Aspect標識的Bean 以及其下的切點和通知方法advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;
}
處理所有切面其下通知方法: BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors
會遍歷所有的Bean找到其中被注解 @Aspect 標識的,然后去處理其下的切點和通知方法
public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = this.aspectBeanNames;if (aspectNames == null) {synchronized(this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new ArrayList();List<String> aspectNames = new ArrayList();String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);String[] var18 = beanNames;int var19 = beanNames.length;// 遍歷所有的Beanfor(int var7 = 0; var7 < var19; ++var7) {String beanName = var18[var7];if (this.isEligibleBean(beanName)) {Class<?> beanType = this.beanFactory.getType(beanName, false);// 判斷是否被@Aspect注解標識 標示的就需要去處理其下的切點和通知方法if (beanType != null && this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);// 去獲取其下的切點和通知方法List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);} else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);} // 省略..............}}}this.aspectBeanNames = aspectNames;return advisors;}}}// 省略..............
}
獲取切面下所有的通知方法
ReflectiveAspectJAdvisorFactory.getAdvisors
遍歷切面下的所有方法,去找方法上是否有相應的注解,如果有則需要封裝處理
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();this.validate(aspectClass);MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new ArrayList();// 獲取切面下的所有方法Iterator var6 = this.getAdvisorMethods(aspectClass).iterator();// 遍歷所有方法while(var6.hasNext()) {Method method = (Method)var6.next();// 判斷該方法是否被相關注解標識 標識的方法處理后封裝返回Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);if (advisor != null) {advisors.add(advisor);}}// 省略......return advisors;}
獲取具體通知方法:ReflectiveAspectJAdvisorFactory.getAdvisor
遍歷我需要的注解,在方法上找注解是否存在,存在的就需要封裝處理
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());// 獲取方法上的注解 實際就是遍歷需要的注解 一個個找AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());// 沒有對應的注解就返回null 有對應的注解就需要處理封裝后返回return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {// 看下面方法AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;} else {// 找到了就設置一下切點上的表達式AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);ajexp.setExpression(aspectJAnnotation.getPointcutExpression());if (this.beanFactory != null) {ajexp.setBeanFactory(this.beanFactory);}return ajexp;}
}
// ASPECTJ_ANNOTATION_CLASSES = new Class[]{Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
protected static AbstractAspectJAdvisorFactory.AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {// 遍歷需要的注解,一個一個找Class[] var1 = ASPECTJ_ANNOTATION_CLASSES;int var2 = var1.length;for(int var3 = 0; var3 < var2; ++var3) {Class<?> clazz = var1[var3];AbstractAspectJAdvisorFactory.AspectJAnnotation<?> foundAnnotation = findAnnotation(method, clazz);if (foundAnnotation != null) {return foundAnnotation;}}return null;
}
通知方法的封裝
InstantiationModelAwarePointcutAdvisorImpl
這個在構造里面就會對通知方法進行處理封裝
public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {this.declaredPointcut = declaredPointcut;this.declaringClass = aspectJAdviceMethod.getDeclaringClass();this.methodName = aspectJAdviceMethod.getName();this.parameterTypes = aspectJAdviceMethod.getParameterTypes();this.aspectJAdviceMethod = aspectJAdviceMethod;this.aspectJAdvisorFactory = aspectJAdvisorFactory;this.aspectInstanceFactory = aspectInstanceFactory;this.declarationOrder = declarationOrder;this.aspectName = aspectName;if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);this.pointcut = new InstantiationModelAwarePointcutAdvisorImpl.PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);this.lazy = true;} else {this.pointcut = this.declaredPointcut;this.lazy = false;// 封裝通知方法this.instantiatedAdvice = this.instantiateAdvice(this.declaredPointcut);}}
ReflectiveAspectJAdvisorFactory.getAdvice 所有的通知方法都會被封裝成對應處理類
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();this.validate(candidateAspectClass);AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);if (aspectJAnnotation == null) {return null;} else if (!this.isAspect(candidateAspectClass)) {throw new AopConfigException("Advice must be declared inside an aspect type: Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]");} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Found AspectJ method: " + candidateAdviceMethod);}Object springAdvice;// 根據方法上的注解類型 封裝對應的通知方法處理類switch(aspectJAnnotation.getAnnotationType()) {case AtPointcut:if (this.logger.isDebugEnabled()) {this.logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");}return null;case AtAround:springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtBefore:springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfter:springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfterReturning:springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterReturning afterReturningAnnotation = (AfterReturning)aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterReturningAnnotation.returning())) {((AbstractAspectJAdvice)springAdvice).setReturningName(afterReturningAnnotation.returning());}break;case AtAfterThrowing:springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterThrowing afterThrowingAnnotation = (AfterThrowing)aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {((AbstractAspectJAdvice)springAdvice).setThrowingName(afterThrowingAnnotation.throwing());}break;default:throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);}((AbstractAspectJAdvice)springAdvice).setAspectName(aspectName);((AbstractAspectJAdvice)springAdvice).setDeclarationOrder(declarationOrder);String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);if (argNames != null) {((AbstractAspectJAdvice)springAdvice).setArgumentNamesFromStringArray(argNames);}((AbstractAspectJAdvice)springAdvice).calculateArgumentBindings();return (Advice)springAdvice;}
}
通知方法與Bean匹配
:AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply
protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {ProxyCreationContext.setCurrentProxiedBeanName(beanName);List var4;try {// 通知方法集合與Bean匹配var4 = AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);} finally {ProxyCreationContext.setCurrentProxiedBeanName((String)null);}return var4;
}
總結: 所以這一步會找到所有的切面,遍歷其下的所有切點和通知方法,然后根據切點中的表達式去與Bean對象匹配,獲取所有匹配成功的通知方法,將這些通知方法排序后就是最后的方法執行鏈,同時也說明該Bean需要被代理,所以需要創建代理對象
創建代理對象
AbstractAutoProxyCreator.createProxy
這里實際就是在創建代理對象前填充一下必要信息,然后創建代理對象,默認是采用JDK動態代理,如果被代理的目標對象不是接口,則會采用Cglib動態代理
- CglibAopProxy:Cglib動態代理邏輯類
- JdkDynamicAopProxy:Jdk動態代理邏輯類(我們以這個為例)
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 = new ProxyFactory();proxyFactory.copyFrom(this);// 省略一大段...........// 匹配成功的某些通知方法會被包裝成攔截器 上面說過了Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);this.customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (this.advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}ClassLoader classLoader = this.getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader)classLoader).getOriginalClassLoader();}// 上面設置搞定后 就要獲取代理對象 JDK還是Cglibreturn proxyFactory.getProxy(classLoader);}
JdkDynamicAopProxy.getProxy
這一步很簡單就是直接創建代理對象,處理類是this,說明該類本身就是處理類
public Object getProxy(@Nullable ClassLoader classLoader) {if (logger.isTraceEnabled()) {logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());}return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}
代理執行方法
我們以JDK動態代理為例,最終代理對象在執行方法的時候就會調用該方法:
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;Class var8;try {// 省略...........if (method.getDeclaringClass() != DecoratingProxy.class) {Object retVal;// 省略...........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();}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);}Object var12 = retVal;return var12;}var8 = AopProxyUtils.ultimateTargetClass(this.advised);} finally {// 省略...........}return var8;}
原理總結
- AOP代理對象的生成是在Bean實例化前、循環依賴、初始化后這三個位置判斷生成的(以初始化后為主,其他兩個階段屬于特殊階段)
- 通過獲取所有的切面下的通知方法以切點表達式來與Bean匹配,來判斷該Bean是否需要被代理,同時準備好了與該Bean相關的所有增強方法
- AOP默認采用JDK動態代理的方式,如果被代理目標對象不是接口,則會采用Cglib的代理方法
- AOP的底層原理雖然是動態代理,但是我覺得最重要的還是執行的方法調用鏈非常巧妙
- 在邏輯實現上:每種通知在調用鏈上執行的方式及其執行順序決定了其扮演的角色
最后附上個執行結構圖
參考博文
Spring AOP切點表達式(Pointcut)詳解
SpringAop介紹
SpringAop介紹與原理