引
經(jīng)過(guò)前兩篇的分析,已經(jīng)成功創(chuàng)建了目標(biāo)類(lèi)的代理突琳,接著分析代理的調(diào)用過(guò)程。在前面的章節(jié)已經(jīng)介紹過(guò)SpringAOP中的增強(qiáng)類(lèi)型分別有前置增強(qiáng)、后置異常增強(qiáng)、后置返回增強(qiáng)菇绵、后置最終增強(qiáng)欠动、環(huán)繞增強(qiáng)五種類(lèi)型人芽,從名稱(chēng)上我們也可以大致看出來(lái)前置增強(qiáng)一定是先于后置增強(qiáng)被執(zhí)行的,那么SpringAOP是如何保證這幾種增強(qiáng)的執(zhí)行順序呢矢劲?它們的執(zhí)行順序應(yīng)該什么樣呢?
在35--SpringAop創(chuàng)建代理(一) 中已經(jīng)介紹過(guò)Spring在獲取到所有增強(qiáng)之后,還要篩選出來(lái)適合當(dāng)前bean的增強(qiáng)捎谨。
再篩選完之后,其實(shí)還有兩個(gè)很重要的步驟,我們沒(méi)有分析臂寝,那就是在eligibleAdvisors集合首位加入ExposeInvocationInterceptor增強(qiáng)(方法攔截)以及對(duì)增強(qiáng)進(jìn)行排序
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 1、查找所有候選增強(qiáng)
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 2、從所有增強(qiáng)集合中查找適合當(dāng)前bean的增強(qiáng)
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
// 3怜珍、在eligibleAdvisors集合首位加入ExposeInvocationInterceptor增強(qiáng)
// ExposeInvocationInterceptor的作用是可以將當(dāng)前的MethodInvocation暴露為一個(gè)thread-local對(duì)象,該攔截器很少使用
// 使用場(chǎng)景:一個(gè)切點(diǎn)(例如AspectJ表達(dá)式切點(diǎn))需要知道它的全部調(diào)用上線文環(huán)境
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
// 4.對(duì)增強(qiáng)進(jìn)行排序
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
關(guān)于ExposeInvocationInterceptor無(wú)需過(guò)多了解柔袁,注釋里已經(jīng)有所介紹插掂,重點(diǎn)看對(duì)增強(qiáng)的排序原則。
protected List<Advisor> sortAdvisors(List<Advisor> advisors) {
// 1.創(chuàng)建PartiallyComparableAdvisorHolder集合并將所有的增強(qiáng)加入到該集合中
List<PartiallyComparableAdvisorHolder> partiallyComparableAdvisors = new ArrayList<>(advisors.size());
for (Advisor element : advisors) {
partiallyComparableAdvisors.add(new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR));
}
// 2.執(zhí)行排序并返回結(jié)果
List<PartiallyComparableAdvisorHolder> sorted = PartialOrder.sort(partiallyComparableAdvisors);
if (sorted != null) {
List<Advisor> result = new ArrayList<>(advisors.size());
for (PartiallyComparableAdvisorHolder pcAdvisor : sorted) {
result.add(pcAdvisor.getAdvisor());
}
return result;
}
else {
return super.sortAdvisors(advisors);
}
}
具體的排序方法不做過(guò)深入的分析了构回,但是其排序的原則和增強(qiáng)的執(zhí)行順序還是要簡(jiǎn)單介紹一下:
- 如果在一個(gè)切面中沒(méi)有相同類(lèi)型的增強(qiáng)(例如:一個(gè)切面類(lèi)里不同時(shí)有兩個(gè)前置增強(qiáng))患民,且目標(biāo)方法里沒(méi)有異常拋出仅孩,那么其執(zhí)行順序是:環(huán)繞增強(qiáng) --> 前置增強(qiáng) --> 目標(biāo)類(lèi)方法 --> 前置增強(qiáng) --> 后置最終增強(qiáng) --> 后置返回增強(qiáng)
- 如果在一個(gè)切面中沒(méi)有相同類(lèi)型的增強(qiáng)(例如:一個(gè)切面類(lèi)里不同時(shí)有兩個(gè)前置增強(qiáng))赦肃,但目標(biāo)方法里存在異常船侧,那么其執(zhí)行順序是 :環(huán)繞增強(qiáng) --> 前置增強(qiáng) --> 目標(biāo)類(lèi)方法(直至調(diào)用到發(fā)生異常的代碼)--> 后置最終增強(qiáng)--> 后置異常增強(qiáng)
- 如果兩條相同增強(qiáng)類(lèi)型增強(qiáng)來(lái)自同一切面厅各,它們將具有相同的順序队塘。然后根據(jù)以下規(guī)則進(jìn)一步排序锯梁。如果是后置增強(qiáng)蕊肥,那么最后聲明的增強(qiáng)將獲得最高的優(yōu)先級(jí)耘成,對(duì)于其他類(lèi)型的增強(qiáng),首先聲明的增強(qiáng)將獲得最高的優(yōu)先級(jí)
其執(zhí)行順序已經(jīng)了解了瘪菌,下面通過(guò)兩個(gè)實(shí)例才驗(yàn)證一下(另外在前面的文章介紹稍微有些缺陷撒会,這里也修正一下)。
2. 增強(qiáng)調(diào)用順序?qū)嵗?/h5>
- 目標(biāo)類(lèi)
package com.lyc.cn.v2.day07;
public interface Animal {
void sayHello();
void sayException();
}
package com.lyc.cn.v2.day07;
public class Dog implements Animal {
@Override
public void sayHello() {
System.out.println("--被增強(qiáng)的方法");
}
@Override
public void sayException() {
// 手動(dòng)拋出異常
System.out.println("--被增強(qiáng)的方法,準(zhǔn)備拋出異常");
throw new IllegalArgumentException();
}
}
- 切面類(lèi)
package com.lyc.cn.v2.day07;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.junit.experimental.theories.Theory;
/**
* 切面類(lèi)
* @author: LiYanChao
* @create: 2018-10-31 15:46
*/
@Aspect
//@Aspect("perthis(this(com.lyc.cn.v2.day07.Dog))")
//@Aspect("pertarget(this(com.lyc.cn.v2.day07.Dog))")
public class DogAspect {
/**
* 例如:execution (* com.sample.service.impl..*.*(..)
* 1师妙、execution(): 表達(dá)式主體茧彤。
* 2、第一個(gè)*號(hào):表示返回類(lèi)型疆栏,*號(hào)表示所有的類(lèi)型曾掂。
* 3、包名:表示需要攔截的包名壁顶,后面的兩個(gè)點(diǎn)表示當(dāng)前包和當(dāng)前包的所有子包珠洗,
* 即com.sample.service.impl包、子孫包下所有類(lèi)的方法若专。
* 4许蓖、第二個(gè)*號(hào):表示類(lèi)名,*號(hào)表示所有的類(lèi)调衰。
* 5膊爪、*(..):最后這個(gè)星號(hào)表示方法名,*號(hào)表示所有的方法嚎莉,后面括弧里面表示方法的參數(shù)米酬,兩個(gè)點(diǎn)表示任何參數(shù)。
**/
@Pointcut("execution(* com.lyc.cn.v2.day07.*.*(..))")
public void test() {
}
@Before("test()")
public void beforeTest() {
System.out.println("==前置增強(qiáng)");
}
@After("test()")
public void afterTest() {
System.out.println("==后置最終增強(qiáng)");
}
@AfterThrowing(value = "test()", throwing = "th")
public void afterThrowingTest(JoinPoint jp, Throwable th) {
System.out.println("==后置異常增強(qiáng),連接點(diǎn)信息:" + jp.getSignature());
System.out.println("==后置異常增強(qiáng),異常信息:" + th);
}
@AfterReturning("test()")
public void afterReturningTest() {
System.out.println("==后置返回增強(qiáng)");
}
// 如果在環(huán)繞增強(qiáng)里手動(dòng)處理了異常的話,那么后置異常增強(qiáng)是無(wú)法被調(diào)用到的
@Around("test()")
public Object aroundTest(ProceedingJoinPoint p) throws Throwable {
System.out.println("==環(huán)繞增強(qiáng)開(kāi)始");
Object o = p.proceed();
System.out.println("==環(huán)繞增強(qiáng)結(jié)束");
return o;
}
// @DeclareParents(value = "com.lyc.cn.v2.day07.Dog", defaultImpl = IntroduceImpl.class)
// private IIntroduce iIntroduce;
}
- 配置文件
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
1趋箩、proxy-target-class
如果被代理的目標(biāo)對(duì)象至少實(shí)現(xiàn)了一個(gè)接口赃额,則會(huì)使用JDK動(dòng)態(tài)代理加派,所有實(shí)現(xiàn)該目標(biāo)類(lèi)實(shí)現(xiàn)的接口都將被代理
如果該目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)任何接口,則創(chuàng)建CGLIB動(dòng)態(tài)代理跳芳。
但是可以通過(guò)proxy-target-class屬性強(qiáng)制指定使用CGLIB代理芍锦,
2、expose-proxy
解決目標(biāo)對(duì)象內(nèi)部的自我調(diào)用無(wú)法實(shí)施切面增強(qiáng)的問(wèn)題
-->
<aop:aspectj-autoproxy>
<!-- 指定@Aspect類(lèi)飞盆,支持正則表達(dá)式娄琉,符合該表達(dá)式的切面類(lèi)才會(huì)被應(yīng)用-->
<!--<aop:include name="dogAspect"></aop:include>-->
</aop:aspectj-autoproxy>
<!--AspectJ-->
<bean name="dogAspect" class="com.lyc.cn.v2.day07.DogAspect"/>
<!--<bean name="catAspect" class="com.lyc.cn.v2.day07.CatAspect"/>-->
<!--bean-->
<bean id="dog" class="com.lyc.cn.v2.day07.Dog"/>
</beans>
- 測(cè)試方法
@Test
public void test1() {
// 基于@AspectJ注解方式
ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day07.xml");
Animal dog = ctx.getBean("dog", Animal.class);
dog.sayHello();
}
@Test
public void test5() {
// 基于@AspectJ注解方式
ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day07.xml");
Animal dog = ctx.getBean("dog", Animal.class);
dog.sayException();
}
package com.lyc.cn.v2.day07;
public interface Animal {
void sayHello();
void sayException();
}
package com.lyc.cn.v2.day07;
public class Dog implements Animal {
@Override
public void sayHello() {
System.out.println("--被增強(qiáng)的方法");
}
@Override
public void sayException() {
// 手動(dòng)拋出異常
System.out.println("--被增強(qiáng)的方法,準(zhǔn)備拋出異常");
throw new IllegalArgumentException();
}
}
package com.lyc.cn.v2.day07;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.junit.experimental.theories.Theory;
/**
* 切面類(lèi)
* @author: LiYanChao
* @create: 2018-10-31 15:46
*/
@Aspect
//@Aspect("perthis(this(com.lyc.cn.v2.day07.Dog))")
//@Aspect("pertarget(this(com.lyc.cn.v2.day07.Dog))")
public class DogAspect {
/**
* 例如:execution (* com.sample.service.impl..*.*(..)
* 1师妙、execution(): 表達(dá)式主體茧彤。
* 2、第一個(gè)*號(hào):表示返回類(lèi)型疆栏,*號(hào)表示所有的類(lèi)型曾掂。
* 3、包名:表示需要攔截的包名壁顶,后面的兩個(gè)點(diǎn)表示當(dāng)前包和當(dāng)前包的所有子包珠洗,
* 即com.sample.service.impl包、子孫包下所有類(lèi)的方法若专。
* 4许蓖、第二個(gè)*號(hào):表示類(lèi)名,*號(hào)表示所有的類(lèi)调衰。
* 5膊爪、*(..):最后這個(gè)星號(hào)表示方法名,*號(hào)表示所有的方法嚎莉,后面括弧里面表示方法的參數(shù)米酬,兩個(gè)點(diǎn)表示任何參數(shù)。
**/
@Pointcut("execution(* com.lyc.cn.v2.day07.*.*(..))")
public void test() {
}
@Before("test()")
public void beforeTest() {
System.out.println("==前置增強(qiáng)");
}
@After("test()")
public void afterTest() {
System.out.println("==后置最終增強(qiáng)");
}
@AfterThrowing(value = "test()", throwing = "th")
public void afterThrowingTest(JoinPoint jp, Throwable th) {
System.out.println("==后置異常增強(qiáng),連接點(diǎn)信息:" + jp.getSignature());
System.out.println("==后置異常增強(qiáng),異常信息:" + th);
}
@AfterReturning("test()")
public void afterReturningTest() {
System.out.println("==后置返回增強(qiáng)");
}
// 如果在環(huán)繞增強(qiáng)里手動(dòng)處理了異常的話,那么后置異常增強(qiáng)是無(wú)法被調(diào)用到的
@Around("test()")
public Object aroundTest(ProceedingJoinPoint p) throws Throwable {
System.out.println("==環(huán)繞增強(qiáng)開(kāi)始");
Object o = p.proceed();
System.out.println("==環(huán)繞增強(qiáng)結(jié)束");
return o;
}
// @DeclareParents(value = "com.lyc.cn.v2.day07.Dog", defaultImpl = IntroduceImpl.class)
// private IIntroduce iIntroduce;
}
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
1趋箩、proxy-target-class
如果被代理的目標(biāo)對(duì)象至少實(shí)現(xiàn)了一個(gè)接口赃额,則會(huì)使用JDK動(dòng)態(tài)代理加派,所有實(shí)現(xiàn)該目標(biāo)類(lèi)實(shí)現(xiàn)的接口都將被代理
如果該目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)任何接口,則創(chuàng)建CGLIB動(dòng)態(tài)代理跳芳。
但是可以通過(guò)proxy-target-class屬性強(qiáng)制指定使用CGLIB代理芍锦,
2、expose-proxy
解決目標(biāo)對(duì)象內(nèi)部的自我調(diào)用無(wú)法實(shí)施切面增強(qiáng)的問(wèn)題
-->
<aop:aspectj-autoproxy>
<!-- 指定@Aspect類(lèi)飞盆,支持正則表達(dá)式娄琉,符合該表達(dá)式的切面類(lèi)才會(huì)被應(yīng)用-->
<!--<aop:include name="dogAspect"></aop:include>-->
</aop:aspectj-autoproxy>
<!--AspectJ-->
<bean name="dogAspect" class="com.lyc.cn.v2.day07.DogAspect"/>
<!--<bean name="catAspect" class="com.lyc.cn.v2.day07.CatAspect"/>-->
<!--bean-->
<bean id="dog" class="com.lyc.cn.v2.day07.Dog"/>
</beans>
@Test
public void test1() {
// 基于@AspectJ注解方式
ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day07.xml");
Animal dog = ctx.getBean("dog", Animal.class);
dog.sayHello();
}
@Test
public void test5() {
// 基于@AspectJ注解方式
ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day07.xml");
Animal dog = ctx.getBean("dog", Animal.class);
dog.sayException();
}
這樣準(zhǔn)備工作做完了,來(lái)看具體的測(cè)試結(jié)果:
- 沒(méi)有異常拋出
==環(huán)繞增強(qiáng)開(kāi)始
==前置增強(qiáng)
--被增強(qiáng)的方法
==環(huán)繞增強(qiáng)結(jié)束
==后置最終增強(qiáng)
==后置返回增強(qiáng)
- 有異常拋出
==環(huán)繞增強(qiáng)開(kāi)始
==前置增強(qiáng)
--被增強(qiáng)的方法,準(zhǔn)備拋出異常
==后置最終增強(qiáng)
==后置異常增強(qiáng),連接點(diǎn)信息:void com.lyc.cn.v2.day07.Animal.sayException()
==后置異常增強(qiáng),異常信息:java.lang.IllegalArgumentException
對(duì)于增強(qiáng)的執(zhí)行順序我們已經(jīng)有所了解吓歇,下面通過(guò)分析源碼车胡,來(lái)看一下Spring是如何執(zhí)行代理方法調(diào)用的。
3.JDK動(dòng)態(tài)代理調(diào)用過(guò)程(假設(shè)無(wú)異常拋出情況下)
這里照瘾,如何進(jìn)入到其調(diào)用源碼里呢匈棘,我們可以在前面的測(cè)試類(lèi)dog.sayHello();
這句話前面打斷點(diǎn),然后步入斷點(diǎn)就OK了析命。
/**
* 調(diào)用JDK動(dòng)態(tài)代理invoke方法
* Implementation of {@code InvocationHandler.invoke}.
* <p>Callers will see exactly the exception thrown by the target,
* unless a hook method throws an exception.
*/
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// 1主卫、處理equals方法,如果接口中沒(méi)有定義equals而在實(shí)現(xiàn)類(lèi)中覆蓋了equals方法鹃愤,那么該equals方法不會(huì)被增強(qiáng)
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
// 2簇搅、處理hashCode方法,如果接口中沒(méi)有定義hashCode而在實(shí)現(xiàn)類(lèi)中覆蓋了hashCode方法软吐,那么該hashCode方法不會(huì)被增強(qiáng)
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
// 3瘩将、如果目標(biāo)對(duì)象是DecoratingProxy類(lèi)型赠群,則返回目標(biāo)對(duì)象的最終對(duì)象類(lèi)型
// DecoratingProxy接口只有一個(gè)getDecoratedClass方法蚌父,用于返回目標(biāo)對(duì)象的最終對(duì)象類(lèi)型
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
// 4、如果目標(biāo)對(duì)象是Advice類(lèi)型岸军,則直接使用反射進(jìn)行調(diào)用
// opaque-->標(biāo)記是否需要阻止通過(guò)該配置創(chuàng)建的代理對(duì)象轉(zhuǎn)換為Advised類(lèi)型肖抱,默認(rèn)值為false备典,表示代理對(duì)象可以被轉(zhuǎn)換為Advised類(lèi)型
// method.getDeclaringClass().isInterface()-->目標(biāo)對(duì)象是接口
// method.getDeclaringClass().isAssignableFrom(Advised.class)-->
// 是用來(lái)判斷一個(gè)類(lèi)Class1和另一個(gè)類(lèi)Class2是否相同或者Class1類(lèi)是不是Class2的父類(lèi)。例如:Class1.isAssignableFrom(Class2)
else if (!this.advised.opaque
&& method.getDeclaringClass().isInterface()
&& method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
// 5意述、解決目標(biāo)對(duì)象內(nèi)部自我調(diào)用無(wú)法實(shí)施切面增強(qiáng)提佣,在這里暴露代理
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
// 6、獲取當(dāng)前方法的攔截器鏈荤崇,并執(zhí)行調(diào)用
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 檢測(cè)是否攔截器鏈?zhǔn)欠駷榭瞻杵粒绻麛r截器鏈為空,那么直接通過(guò)反射調(diào)用目標(biāo)對(duì)象的方法术荤,避免創(chuàng)建MethodInvocation
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
// 通過(guò)反射直接調(diào)用目標(biāo)對(duì)象的方法
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// 創(chuàng)建MethodInvocation對(duì)象并調(diào)用proceed方法倚喂,攔截器鏈被封裝到了invocation中
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
// 調(diào)用攔截器鏈
retVal = invocation.proceed();
}
// 7、返回結(jié)果
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null
&& retVal == target
&& returnType != Object.class
&& returnType.isInstance(proxy)
&& !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
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()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
具體的執(zhí)行過(guò)程喜每,注釋里已經(jīng)分析的很清楚了务唐,最核心的就是獲取攔截器鏈和執(zhí)行攔截器鏈調(diào)用雳攘。留在下一章分析带兜。