一、AOP的核心概念回顧
https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#aop
我們先來看一下下面的這張圖
說明:
程序運行時會調用很多方法,調用的很多方法就叫做Join points(連接點,可以被選擇來進行增強的方法點),在方法的前或者后選擇一個地方來切入,切入的的地方就叫做Pointcut(切入點,選擇增強的方法),然后把要增強的功能(Advice)加入到切入點所在的位置。Advice和Pointcut組成一個切面(Aspect)
AOP的幾個概念:
Advice、Pointcut、Weaving的特點:
Advice(功能增強):
1)用戶性:由用戶提供增強功能的邏輯代碼
2)變化的:不同的增強需求,會有不同的邏輯
3)可選時機:可選擇在方法前、后、異常時進行功能增強
4)多重的:同一個切入點上可以有多重增強
Pointcut(切入點):
1)用戶性:由用戶來指定
2)變化的:用戶可靈活指定
3)多點性:用戶可以選擇在多個點上進行功能增強
Weaving(織入):
1)無侵入性,因為不改變原類的代碼
2)我們在框架中實現
二、Spring中AOP的用法
1. 傳統Advisor方式
掌握用法:
1)編程提供Advice,實現對應的Advice接口
2)配置Advisor(advice+pointcut)
Advice接口:
示例代碼:
被增強的目標對象:
BeanQ
package com.study.leesmall.spring.sample.aop; //被增強的目標對象 public class BeanQ { public void do1(String task, int time) { System.out.println("-------------do1 do " + task + " time:" + time); } public String service1(String name) { System.out.println("-------------servce1 do " + name); return name; } public String service2(String name) { System.out.println("-------------servce2 do " + name); if (!"s1".equals(name)) { throw new IllegalArgumentException("參數 name != s1, name=" + name); } return name + " hello!"; } }
編程提供Advice,實現對應的Advice接口:
前置增強:
MyBeforeAdvice
package com.study.leesmall.spring.sample.aop; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; //前置增強 public class MyBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("------ MyBeforeAdvice before 增強 " + target + " " + method); } }
環繞增強:
MyArroundAdvice
package com.study.leesmall.spring.sample.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; //環繞增強 public class MyArroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("--------- 環繞 -前增強"); Object ret = invocation.proceed(); System.out.println("--------- 環繞 -后增強"); return ret; } }
在/spring-source-study/src/main/java/com/study/leesmall/spring/sample/aop/application.xml里面配置Advisor(advice+pointcut):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 傳統方式的aop begin --> <!--被增強的目標對象 --> <bean id="BeanQ" class="com.study.leesmall.spring.sample.aop.BeanQ" /> <!--配置advice --> <bean id="myBeforeAdvice" class="com.study.leesmall.spring.sample.aop.MyBeforeAdvice" /> <bean id="yyArroundAdvice" class="com.study.leesmall.spring.sample.aop.MyArroundAdvice" /> <!--配置pointcut --> <aop:config >
<!--全局切入點,任何一個aop-config都可以使用 --> <aop:pointcut id="doMethods" expression="execution(* com.study.leesmall.spring.sample.aop.*.do*(..))" /> <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="doMethods" /> <aop:advisor advice-ref="yyArroundAdvice" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.service*(..))"/> </aop:config> <!-- 傳統方式的aop end --> </beans>
配置文件里面的注意點:
<aop:config> 的屬性了解
proxy-target-class="false"使用jdk的動態代理 默認配置
proxy-target-class="true"使用cglib的動態代理
掌握 spring Aop 的 API:
Advice:
Advisor的代碼:
* Copyright 2002-2017 the original author or authors. package org.springframework.aop; import org.aopalliance.aop.Advice; /** * Base interface holding AOP <b>advice</b> (action to take at a joinpoint) * and a filter determining the applicability of the advice (such as * a pointcut). <i>This interface is not for use by Spring users, but to * allow for commonality in support for different types of advice.</i> * * <p>Spring AOP is based around <b>around advice</b> delivered via method * <b>interception</b>, compliant with the AOP Alliance interception API. * The Advisor interface allows support for different types of advice, * such as <b>before</b> and <b>after</b> advice, which need not be * implemented using interception. * * @author Rod Johnson * @author Juergen Hoeller */ public interface Advisor { /** * Common placeholder for an empty {@code Advice} to be returned from * {@link #getAdvice()} if no proper advice has been configured (yet). * @since 5.0 */ Advice EMPTY_ADVICE = new Advice() {}; /** * Return the advice part of this aspect. An advice may be an * interceptor, a before advice, a throws advice, etc. * @return the advice that should apply if the pointcut matches * @see org.aopalliance.intercept.MethodInterceptor * @see BeforeAdvice * @see ThrowsAdvice * @see AfterReturningAdvice */ Advice getAdvice(); /** * Return whether this advice is associated with a particular instance * (for example, creating a mixin) or shared with all instances of * the advised class obtained from the same Spring bean factory. * <p><b>Note that this method is not currently used by the framework.</b> * Typical Advisor implementations always return {@code true}. * Use singleton/prototype bean definitions or appropriate programmatic * proxy creation to ensure that Advisors have the correct lifecycle model. * @return whether this advice is associated with a particular target instance */ boolean isPerInstance(); }
Pointcut:
Pointcut的子類:
Advisor的子類:
PointcutAdvisor擴展了Advisor以后就會有一個切面Aspect=Advice+Pointcut
PointcutAdvisor的代碼:
* Copyright 2002-2012 the original author or authors. package org.springframework.aop; /** * Superinterface for all Advisors that are driven by a pointcut. * This covers nearly all advisors except introduction advisors, * for which method-level matching doesn't apply. * * @author Rod Johnson */ public interface PointcutAdvisor extends Advisor { /** * Get the Pointcut that drives this advisor. */ Pointcut getPointcut(); }
PointcutAdvisor的子類:
測試類:
AopMain
package com.study.leesmall.spring.sample.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; public class AopMain { public static void main(String[] args) { ApplicationContext context = new GenericXmlApplicationContext( "classpath:com/study/leesmall/spring/sample/aop/application.xml"); BeanQ bq = context.getBean(BeanQ.class); bq.do1("task1", 20); System.out.println(); bq.service1("service1"); System.out.println(); bq.service2("ssss"); } }
測試結果:
------ MyBeforeAdvice before 增強 com.study.leesmall.spring.sample.aop.BeanQ@1d119efb public void com.study.leesmall.spring.sample.aop.BeanQ.do1(java.lang.String,int) -------------do1 do task1 time:20 --------- 環繞 -前增強 -------------servce1 do service1 --------- 環繞 -后增強 --------- 環繞 -前增強 -------------servce2 do ssss Exception in thread "main" java.lang.IllegalArgumentException: 參數 name != s1, name=ssss at com.study.leesmall.spring.sample.aop.BeanQ.service2(BeanQ.java:18) at com.study.leesmall.spring.sample.aop.BeanQ$$FastClassBySpringCGLIB$$3d1515ac.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at com.study.leesmall.spring.sample.aop.MyArroundAdvice.invoke(MyArroundAdvice.java:12) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) at com.study.leesmall.spring.sample.aop.BeanQ$$EnhancerBySpringCGLIB$$2fc5343.service2(<generated>) at com.study.leesmall.spring.sample.aop.AopMain.main(AopMain.java:18)
2. Aspect語法方式
Aspect的advice是基於方法的。
掌握用法:
1)定義包含Advice方法的Bean類
2)配置Bean定義
3)配置Aspect(引用包含advice方法的bean),在里面配置各種Advice(method+pointcut)
定義包含Advice方法的Bean類:
AspectAdviceBean:
package com.study.leesmall.spring.sample.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; //定義包含 Advice 方法的 Bean 類 public class AspectAdviceBean { public void before1() { System.out.println("----------- AspectAdviceBean before1 增強 "); } //JoinPoint看具體哪個方法被增強了,JoinPoint一定要放在第一個參數 public void before2(JoinPoint jp) { System.out.println("----------- AspectAdviceBean before2 增強 for " + jp); } //調用被增強的方法時傳參數 //<aop:before method="before3" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.do*(..)) //and args(tk,..)" arg-names=""/> //args(tk,..)有兩個意思,第一個意思是被增強的方法的第一個參數的類型要和before3的參數tk的類型一樣 //第二個意思是被增強的方法的第一個參數tk要賦值給before3的參數tk //arg-names="" 當不能確定方法參數的順序時可以用這個參數指定arg-names="param1,param2" public void before3(String tk) { System.out.println("----------- AspectAdviceBean before3 增強 參數tk= " + tk); } //調用被增強的方法時傳參數 public void before4(String tk, int ti) { System.out.println("----------- AspectAdviceBean before4 增強 參數tk= " + tk + " ti=" + ti); } //ProceedingJoinPoint正在處理的方法 public Object arround1(ProceedingJoinPoint pjp) throws Throwable { System.out.println("----------- AspectAdviceBean arround1 環繞-前增強 for " + pjp); Object ret = pjp.proceed(); System.out.println("----------- AspectAdviceBean arround1 環繞-后增強 for " + pjp); return ret; } public Object arround2(ProceedingJoinPoint pjp, String name) throws Throwable { System.out.println("--------- AspectAdviceBean arround2 參數 name=" + name); System.out.println("----------- AspectAdviceBean arround2 環繞-前增強 for " + pjp); Object ret = pjp.proceed(); System.out.println("----------- AspectAdviceBean arround2 環繞-后增強 for " + pjp); return ret; } public void afterReturning(Object retValue) { System.out.println("----------- AspectAdviceBean afterReturning 增強 , 返回值為: " + retValue); } public void afterThrowing(JoinPoint jp, Exception e) { System.out.println("----------- AspectAdviceBean afterThrowing 增強 for " + jp); System.out.println("----------- AspectAdviceBean afterThrowing 增強 異常 :" + e); } public void after(JoinPoint jp) { System.out.println("----------- AspectAdviceBean after 增強 for " + jp); } }
JoinPoint和ProceedingJoinPoint :
在/spring-source-study/src/main/java/com/study/leesmall/spring/sample/aop/application.xml里面配置Bean定義
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 傳統方式的aop begin --> <!--被增強的目標對象 --> <bean id="BeanQ" class="com.study.leesmall.spring.sample.aop.BeanQ" /> <!--配置advice --> <bean id="myBeforeAdvice" class="com.study.leesmall.spring.sample.aop.MyBeforeAdvice" /> <bean id="yyArroundAdvice" class="com.study.leesmall.spring.sample.aop.MyArroundAdvice" /> <!--配置pointcut --> <aop:config > <aop:pointcut id="doMethods" expression="execution(* com.study.leesmall.spring.sample.aop.*.do*(..))" /> <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="doMethods" /> <aop:advisor advice-ref="yyArroundAdvice" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.service*(..))"/> </aop:config> <!-- 傳統方式的aop end --> <!-- AspectJ的aop begin --> <!-- 配置了包含advice方法的Bean --> <bean id="aspectAdviceBean" class="com.study.leesmall.spring.sample.aop.AspectAdviceBean" /> </beans>
3)配置Aspect(引用包含advice方法的bean),在里面配置各種Advice(method+pointcut)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 傳統方式的aop begin --> <!--被增強的目標對象 --> <bean id="BeanQ" class="com.study.leesmall.spring.sample.aop.BeanQ" /> <!--配置advice --> <bean id="myBeforeAdvice" class="com.study.leesmall.spring.sample.aop.MyBeforeAdvice" /> <bean id="yyArroundAdvice" class="com.study.leesmall.spring.sample.aop.MyArroundAdvice" /> <!--配置pointcut --> <aop:config > <aop:pointcut id="doMethods" expression="execution(* com.study.leesmall.spring.sample.aop.*.do*(..))" /> <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="doMethods" /> <aop:advisor advice-ref="yyArroundAdvice" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.service*(..))"/> </aop:config> <!-- 傳統方式的aop end --> <!-- AspectJ的aop begin --> <!-- 配置了包含advice方法的Bean --> <bean id="aspectAdviceBean" class="com.study.leesmall.spring.sample.aop.AspectAdviceBean" /> <!--配置 Aspect (引用包含 advice 方法的 bean),在里面配置各種 Advice(method + pointcut) --> <aop:config> <aop:pointcut id="services" expression="execution(* com.study.leesmall.spring.sample.aop.*.service*(..))" /> <aop:aspect id="a1" ref="aspectAdviceBean" order="1"> <aop:before method="before1" pointcut-ref="doMethods" /> <aop:before method="before2" pointcut-ref="doMethods"/> <!--args(tk,..)有兩個意思,第一個意思是被增強的方法的第一個參數的類型要和before3的參數tk的類型一樣 第二個意思是被增強的方法的第一個參數tk要賦值給before3的參數tk; arg-names="" 當不能確定方法參數的順序時可以用這個參數指定arg-names="param1,param2" --> <aop:before method="before3" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.do*(..)) and args(tk,..)"/> <aop:before method="before4" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.do*(..)) and args(tk,ti)"/> <aop:around method="arround1" pointcut-ref="services"/> <aop:around method="arround2" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.service*(..)) and args(name)"/> <aop:after-returning method="afterReturning" pointcut-ref="services" returning="retValue"/> <aop:after-throwing method="afterThrowing" pointcut-ref="services" throwing="e"/> <aop:after method="after" pointcut-ref="services"/> </aop:aspect> </aop:config> <!-- AspectJ的aop end --> </beans>
被增強的目標對象:
package com.study.leesmall.spring.sample.aop; //被增強的目標對象 public class BeanQ { public void do1(String task, int time) { System.out.println("-------------do1 do " + task + " time:" + time); } public String service1(String name) { System.out.println("-------------servce1 do " + name); return name; } public String service2(String name) { System.out.println("-------------servce2 do " + name); /**if (!"s1".equals(name)) { throw new IllegalArgumentException("參數 name != s1, name=" + name); }**/ return name + " hello!"; } }
測試類:
AopMain
package com.study.leesmall.spring.sample.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; public class AopMain { public static void main(String[] args) { ApplicationContext context = new GenericXmlApplicationContext( "classpath:com/study/leesmall/spring/sample/aop/application.xml"); BeanQ bq = context.getBean(BeanQ.class); bq.do1("task1", 20); System.out.println(); bq.service1("service1"); System.out.println(); bq.service2("ssss"); } }
測試結果:
----------- AspectAdviceBean before1 增強 ----------- AspectAdviceBean before2 增強 for execution(void com.study.mike.spring.sample.aop.BeanQ.do1(leesmall,int)) ----------- AspectAdviceBean before3 增強 參數tk= task1 ----------- AspectAdviceBean before4 增強 參數tk= task1 ti=20 ------ MyBeforeAdvice before 增強 com.study.mike.spring.sample.aop.BeanQ@3a0baae5 public void com.study.mike.spring.sample.aop.BeanQ.do1(java.lang.leesmall,int) -------------do1 do task1 time:20 ----------- AspectAdviceBean arround1 環繞-前增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service1(leesmall)) --------- AspectAdviceBean arround2 參數 name=service1 ----------- AspectAdviceBean arround2 環繞-前增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service1(leesmall)) --------- 環繞 -前增強 -------------servce1 do service1 --------- 環繞 -后增強 ----------- AspectAdviceBean arround2 環繞-后增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service1(leesmall)) ----------- AspectAdviceBean arround1 環繞-后增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service1(leesmall)) ----------- AspectAdviceBean afterReturning 增強 , 返回值為: service1 ----------- AspectAdviceBean after 增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service1(leesmall)) ----------- AspectAdviceBean arround1 環繞-前增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service2(leesmall)) --------- AspectAdviceBean arround2 參數 name=ssss ----------- AspectAdviceBean arround2 環繞-前增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service2(leesmall)) --------- 環繞 -前增強 -------------servce2 do ssss --------- 環繞 -后增強 ----------- AspectAdviceBean arround2 環繞-后增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service2(leesmall)) ----------- AspectAdviceBean arround1 環繞-后增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service2(leesmall)) ----------- AspectAdviceBean afterReturning 增強 , 返回值為: ssss hello! ----------- AspectAdviceBean after 增強 for execution(leesmall com.study.mike.spring.sample.aop.BeanQ.service2(leesmall))
3. AspectJ注解方式
1)先在pom.xml文件里面引入AspectJ的依賴
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.1</version> </dependency>
2)被增強的目標對象
package com.study.leesmall.spring.sample.aop; //被增強的目標對象 public class BeanQ { public void do1(String task, int time) { System.out.println("-------------do1 do " + task + " time:" + time); } public String service1(String name) { System.out.println("-------------servce1 do " + name); return name; } public String service2(String name) { System.out.println("-------------servce2 do " + name); if (!"s1".equals(name)) { throw new IllegalArgumentException("參數 name != s1, name=" + name); } return name + " hello!"; } }
3)AspectJ注解方式實現AOP
AspectAdviceBeanUseAnnotation
package com.study.leesmall.spring.sample.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; //AspectJ注解方式 @Aspect public class AspectAdviceBeanUseAnnotation { // 定義一個全局的Pointcut @Pointcut("execution(* com.study.leesmall.spring.sample.aop.*.do*(..))") public void doMethods() { } // 定義一個全局的Pointcut @Pointcut("execution(* com.study.leesmall.spring.sample.aop.*.service*(..))") public void services() { } // 定義一個Before Advice @Before("doMethods() and args(tk,..)") public void before3(String tk) { System.out.println("----------- AspectAdviceBeanUseAnnotation before3 增強 參數tk= " + tk); } //環繞增強 @Around("services() and args(name,..)") public Object around2(ProceedingJoinPoint pjp, String name) throws Throwable { System.out.println("--------- AspectAdviceBeanUseAnnotation arround2 參數 name=" + name); System.out.println("----------- AspectAdviceBeanUseAnnotation arround2 環繞-前增強 for " + pjp); Object ret = pjp.proceed(); System.out.println("----------- AspectAdviceBeanUseAnnotation arround2 環繞-后增強 for " + pjp); return ret; } @AfterReturning(pointcut = "services()", returning = "retValue") public void afterReturning(Object retValue) { System.out.println("----------- AspectAdviceBeanUseAnnotation afterReturning 增強 , 返回值為: " + retValue); } @AfterThrowing(pointcut = "services()", throwing = "e") public void afterThrowing(JoinPoint jp, Exception e) { System.out.println("----------- AspectAdviceBeanUseAnnotation afterThrowing 增強 for " + jp); System.out.println("----------- AspectAdviceBeanUseAnnotation afterThrowing 增強 異常 :" + e); } @After("doMethods()") public void after(JoinPoint jp) { System.out.println("----------- AspectAdviceBeanUseAnnotation after 增強 for " + jp); } }
4)在/spring-source-study/src/main/java/com/study/leesmall/spring/sample/aop/application2.xml里面配置bean和開啟AspectJ注解的支持
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--被增強的目標對象 --> <bean id="BeanQ" class="com.study.leesmall.spring.sample.aop.BeanQ" /> <!--AspectJ注解方式實現的AOP --> <bean id="aspectAdviceBeanUseAnnotation" class="com.study.leesmall.spring.sample.aop.AspectAdviceBeanUseAnnotation" /> <!--開啟AspectJ注解的支持 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
開啟@Aspectj注解方式支持:
Xml中<aop:aspectj-autoproxy></aop:aspectj-autoproxy>注意了解它的屬性、及子元素
注解方式開啟:
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
5)測試類
package com.study.leesmall.spring.sample.aop; import org.springframework.context.ApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; public class AopMainUseAspectAnnotation { public static void main(String[] args) { ApplicationContext context = new GenericXmlApplicationContext( "classpath:com/study/leesmall/spring/sample/aop/application2.xml"); BeanQ bq = context.getBean(BeanQ.class); bq.do1("task1", 20); System.out.println(); bq.service1("service1"); } }
7)測試結果
----------- AspectAdviceBeanUseAnnotation before3 增強 參數tk= task1 -------------do1 do task1 time:20 ----------- AspectAdviceBeanUseAnnotation after 增強 for execution(void com.study.leesmall.spring.sample.aop.BeanQ.do1(String,int)) --------- AspectAdviceBeanUseAnnotation arround2 參數 name=service1 ----------- AspectAdviceBeanUseAnnotation arround2 環繞-前增強 for execution(String com.study.leesmall.spring.sample.aop.BeanQ.service1(String)) -------------servce1 do service1 ----------- AspectAdviceBeanUseAnnotation arround2 環繞-后增強 for execution(String com.study.leesmall.spring.sample.aop.BeanQ.service1(String)) ----------- AspectAdviceBeanUseAnnotation afterReturning 增強 , 返回值為: service1
三、Spring AOP 源碼學習
1、spring aop的工作流程是怎樣?以傳統的Advisor配置為例進行思考
2. 源碼閱讀思路
1、先看配置解析,看標簽解析過程都做了什么、完成了什么。
入口:
E:\repository\org\springframework\spring-aop\5.1.3.RELEASE\spring-aop-5.1.3.RELEASE.jar/META-INF/spring.handlers
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
org.springframework.aop.config.AopNamespaceHandler:
a)解析<aop:config> 及它的子元素
org.springframework.aop.config.ConfigBeanDefinitionParser:
1)注冊了autoProxyCreator的Bean定義,它是一個ProxyConfig、BeanFactoryAwrare、BeanPostProcessor
下面來看一下配置自動代理的創建者的代碼:
org.springframework.aop.config.ConfigBeanDefinitionParser.configureAutoProxyCreator(ParserContext, Element)
->
org.springframework.aop.config.AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(ParserContext, Element)
->
org.springframework.aop.config.AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry, Object)
->
org.springframework.aop.config.AopConfigUtils.registerOrEscalateApcAsRequired(Class<?>, BeanDefinitionRegistry, Object)
所以說自動代理創建者是一個ProxyConfig、BeanFactoryAwrare、BeanPostProcessor
->
org.springframework.aop.config.AopConfigUtils.findPriorityForClass(String)
說明:
org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator現在已經不使用了
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator對應於AspectJ的xml方式
<aop:config> <aop:pointcut id="services" expression="execution(* com.study.leesmall.spring.sample.aop.*.service*(..))" /> <aop:aspect id="a1" ref="aspectAdviceBean" order="1"> <aop:before method="before1" pointcut-ref="doMethods" /> <aop:before method="before2" pointcut-ref="doMethods"/> <!--args(tk,..)有兩個意思,第一個意思是被增強的方法的第一個參數的類型要和before3的參數tk的類型一樣 第二個意思是被增強的方法的第一個參數tk要賦值給before3的參數tk; arg-names="" 當不能確定方法參數的順序時可以用這個參數指定arg-names="param1,param2" --> <aop:before method="before3" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.do*(..)) and args(tk,..)" arg-names=""/> <aop:before method="before4" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.do*(..)) and args(tk,ti)"/> <aop:around method="arround1" pointcut-ref="services"/> <aop:around method="arround2" pointcut="execution(* com.study.leesmall.spring.sample.aop.*.service*(..)) and args(name)"/> <aop:after-returning method="afterReturning" pointcut-ref="services" returning="retValue"/> <aop:after-throwing method="afterThrowing" pointcut-ref="services" throwing="e"/> <aop:after method="after" pointcut-ref="services"/> </aop:aspect> </aop:config>
查看org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator的繼承體系:
父類:
子類:
查看org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator的繼承體系,搞清楚它繼承實現了什么,他的父類和子類有哪些
父類:
子類:
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator對應於AspectJ的注解方式:
<bean id="aspectAdviceBeanUseAnnotation" class="com.study.leesamll.spring.sample.aop.AspectAdviceBeanUseAnnotation" /> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<aop:config proxy-target-class="false" expose-proxy="false">對應代碼:
org.springframework.aop.config.AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(ParserContext, Element)
->
org.springframework.aop.config.AopNamespaceUtils.useClassProxyingIfNecessary(BeanDefinitionRegistry, Element)
2)兩種配置方式(advisor和aspectJ)解析之后都是向Bean工廠注冊了Pointcut和Advisor的Bean定義
b)<aop:aspectj-autoproxy>開啟AspectJ注解支持
1)注冊了autoProxyCreator的Bean定義,它是一個ProxyConfig、BeanFactoryAwrare、BeanPostProcessor
org.springframework.aop.config.AopNamespaceHandler.init()
org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser
org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser.parse(Element, ParserContext)
org.springframework.aop.config.AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext, Element)
<!--開啟AspectJ注解的支持 --> <aop:aspectj-autoproxy> <!-- 滿足name里面的表達式(bean名稱的表達式)的才進行切面的處理 --> <aop:include name="bean名稱的表達式"/> </aop:aspectj-autoproxy>
<aop:include name="bean名稱的表達式"/>對應源碼:
c)AspectJ注解的切面在哪里讀取加載的?
可能的地方:
BeanDefinitionRegistryPostProcessor x
BeanFactoryPostProcessor x
InstantiationAwareBeanPostProcessor Bean 實例創建前后
BeanPostProcessor
Xml方式的解析:
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator
注解方式的解析:
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
兩者的關系:
public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors()
BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors() 中完成了注解的讀取、Advisor對象的創建及緩存。
org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors()
org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory.getAdvisors(MetadataAwareAspectInstanceFactory)
2、 看織入的過程
a) 在哪里做的織入?
在org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors()里面打個斷點拿到調用棧
入口:
com.study.leesmall.spring.sample.aop.AopMainUseAspectAnnotation
b) 如何判斷 Bean 要不要被創建代理?如何排除 advice Bean 的?
排除 advice Bean :
跳過advice bean 跳過帶有@Aspect注解的自己,不能自己為自己創建代理,否則進入死循環,如AspectAdviceBeanUseAnnotation
org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.shouldSkip(Class<?>, String)
再次進來,是在 PostProcessAfterInitialization()
創建代理的方法:
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(Object, String, Object)
如何判斷 Bean 要不要被創建代理:
c)如何選擇 jdk動態代理 還是cglib的動態代理
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(Class<?>, String, Object[], TargetSource)
org.springframework.aop.framework.ProxyFactory.getProxy(ClassLoader)
org.springframework.aop.framework.ProxyCreatorSupport.createAopProxy()
org.springframework.aop.framework.DefaultAopProxyFactory.createAopProxy(AdvisedSupport)
d) 如何來創建代理的,涉及哪些類,如何協作的。
AutoProxyCreator
ProxyConfig ProcxyFactory
AopProxyFactory
AopProxy
3 、看方法被調用時的增強過程
a) 在代理中如何決定對當前方法合格的 advice 的?
調用的 Advisor 中 Pointcut 進行匹配
b) 如何組織多個 advice 執行的?
責任鏈模式
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(Object, Method, Object[])
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
4、源碼對應的類圖