1. AOP 概念
AOP(Aspect Oriented Programming)浆兰,即面向切面編程层亿。
- 連接點(JoinPoint)
程序執(zhí)行的某個特定位置:如類開始初始化前搭儒、類初始化后提鸟、類某個方法調用前伏伐、調用后、方法拋出異常后鸠补。一個類或一段程序代碼擁有一些具有邊界性質的特定點,這些點中的特定點就稱為“連接點”嘀掸。Spring僅支持方法的連接點紫岩,即僅能在方法調用前、方法調用后睬塌、方法拋出異常時以及方法調用前后這些程序執(zhí)行點織入增強泉蝌。 - 切點(Pointcut)
每個類具有多個連接點,如果一個類擁有15個方法,那么這些方法都是連接點,連接點相當于數據庫中的記錄揩晴,而切點相當于查詢條件勋陪。切點和連接點不是一對一的關系,一個切點可以匹配多個連接點硫兰。Spring AOP的規(guī)則解析引擎負責切點所設定的查詢條件诅愚,找到對應的連接點。其實確切地說劫映,不能稱之為查詢連接點违孝,因為連接點是方法執(zhí)行前、執(zhí)行后等包括方位信息的具體程序執(zhí)行點泳赋,而切點只定位到某個方法上雌桑,所以如果希望定位到具體連接點上,還需要提供方位信息祖今。 - 增強(Advice)
增強是織入到目標類連接點上的一段程序代碼校坑,在Spring中拣技,增強除用于描述一段程序代碼外,還擁有另一個和連接點相關的信息耍目,這便是執(zhí)行點的方位膏斤。結合執(zhí)行點方位信息和切點信息,我們就可以找到特定的連接點制妄。 - 通知器(Advisor)
當我們完成切面增強設計(Advice)和切入點的設計(Pointcut),需要一個對象把他們結合起來掸绞,Advisor 就是起到這個作用,通過Advisor 耕捞,可以確定在哪個Pointcut 使用哪個Advice衔掸。所以一個Advisor包含一個Advice 和 一個Pointcut 信息。
2. 一些疑問
- Spring AOP 增強的代理類 在什么時候創(chuàng)建 俺抽?
- Spring AOP 怎樣為一個類 和 方法 匹配 增強的 敞映?
- Spring AOP 是如何 協(xié)調 前置通知 后置通知 異常通知 返回通知的?
- 切面類 可以被 AOP增強么磷斧?
3. 注冊 AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator是用來處理 當前項目當中 所有 AspectJ 注解的切面類振愿。以及所有的 Spring Advisor ,同時 也是一個 BeanPostProcessor 弛饭。所以 想了解Spring AOP 如何實現(xiàn)冕末,就要先分析 AnnotationAwareAspectJAutoProxyCreator,那么他從何而來侣颂,是如何注冊到容器內的档桃。
注冊AnnotationAwareAspectJAutoProxyCreator方式:
- 使用
EnableAspectJAutoProxy
注解,改注解 又被@Import
注解注釋,向容器中注冊了AspectJAutoProxyRegistrar憔晒,最后由 AspectJAutoProxyRegistrar 向容器中注冊 AnnotationAwareAspectJAutoProxyCreator藻肄。
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//向容器注冊 AnnotationAwareAspectJAutoProxyCreator
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
//對proxyTargetClass 屬性的支持,切換JDK代理 和 CGLIB 代理
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
//是否對 AopContext.currentPoxy支持拒担。
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
- 使用
@EnableAutoConfiguration
注解嘹屯。改注解會向容器注冊Spring.factories文件中聲明的類,其中AopAutoConfiguration是對Spring AOP自動配置的支持从撼。
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
最終還是通過 EnableAspectJAutoProxy 注冊AnnotationAwareAspectJAutoProxyCreator
@EnableAspectJAutoProxy(proxyTargetClass = false)
判斷條件 州弟,可以在 application.properties 中 配置。
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = true)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = false)
public static class CglibAutoProxyConfiguration {
}
}
- xml 標簽
Spring 采用 自定義標簽 <aop:aspectj-autoproxy /> 注冊AnnotationAwareAspectJAutoProxyCreator
4. AnnotationAwareAspectJAutoProxyCreator的初始化
AnnotationAwareAspectJAutoProxyCreator初始化 主要初始一些 組件對象谋逻。
組件包括:
- AspectJAdvisorFactory
根據AspectJ 注釋的切面類 創(chuàng)建 Spring AOP Advisors 的工廠接口呆馁。Advisor增強器的創(chuàng)建 都是由AspectJAdvisorFactory完成。 - BeanFactoryAspectJAdvisorsBuilder
從項目中 查找所有的切面類毁兆,然后使用 AspecJAdvisorFactory 創(chuàng)建 Advisors浙滤。 - advisorRetrievalHelper
對Xml 配置Advisor 的支持。從容器中檢索 所有的 Advisor 類型气堕。
5 . 創(chuàng)建代理類過程
由于 AnnotationAwareAspectJAutoProxyCreator 是一個 BeanPostProcessor 實現(xiàn)類纺腊,Spring 會在對一個對象的初始化前后執(zhí)行 BeanPostProcssor 的接口方法畔咧。Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;就是 創(chuàng)建增強代理對象的方法。該方法被AnnotationAwareAspectJAutoProxyCreator的父類AbstractAutoProxyCreator實現(xiàn)揖膜。
- postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//bean 就是需要 被增強的類誓沸。
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
//防止 bean 多次被增強留攒。
if (!this.earlyProxyReferences.contains(cacheKey)) {
//如果有必要 就進行封裝撕阎,有沒有必要主要取決于 bean 是否需要被增強。
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
- wrapIfNecessary
對給定bean 就行封裝轮纫。
/**
*如果有必要 封裝 bean
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//如果已經處理過
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
//如果不需要增強 就直接返回
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//如果是一個 組件類趁仙,組件類包括Advice洪添,Pointcut,Advisor,AopInfrastructureBean實現(xiàn)類,和 Aspect注解注釋的類雀费。所以 切面類 自己不會被AOP 增強干奢。
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 為bean 獲取和 匹配合適的 增強器
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
//如果 沒有 匹配到增強器 那么就不用創(chuàng)建增強代理類。
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//創(chuàng)建 增強代理類
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
5.1 獲取增強器
-
getAdvicesAndAdvisorsForBean(為bean獲取和匹配合適的增強器)
getAdvicesAndAdvisorsForBean ---> findEligibleAdvisors:為bean獲取和匹配增強器分為兩步:
5.1.1 獲取到所有的增強器
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
//獲取所有的增強器
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//匹配合適的增強器
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
- findCandidateAdvisors 獲取到所有的增強器
調用AnnotationAwareAspectjAutoProxyCreator 的 findCandidateAdvisors@Override protected List<Advisor> findCandidateAdvisors() { // 對xml 配置 Advisor 的支持. List<Advisor> advisors = super.findCandidateAdvisors(); // 搜索容器中所有 @Aspecj 注解申明的 切面類盏袄,構建增強器 advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); return advisors; }
- aspectJAdvisorsBuilder.buildAspectJAdvisors()
代碼比較多忿峻,就不粘出來了,大致邏輯就是
- 獲取容器中已經注冊的BeanNames
- 遍歷 所有已經注冊的Bean,查找 @AspectJ
- 通過AspectJAdvisorFactory獲取Advisor辕羽。怎么獲取的 就不解釋了逛尚。
5.1.2 匹配合適的增強器
匹配合適的增強器,Spring 使用了 AopUtils.canApply刁愿。
匹配邏輯就是:
遍歷 所有增強器黑低,采用增強器的 Poincut 對一個對象的所有方法進行匹配,一旦有方法匹配到酌毡,就返回true.
對方法的匹配需要遍歷 所有方法 一一就行匹配。
5.2 創(chuàng)建 代理類
通過 ProxyFactory 創(chuàng)建動態(tài)代理類蕾管,創(chuàng)建之前 先配置ProxyFactory,設置目標對象枷踏,設置是否對目標對象代理(會影響采用JDK代理或者Cglib代理),設置增強器集合掰曾,等旭蠕。
配置ProxyFactory完成之后就是獲取代理類了,
調用ProxyFactory.getProxy:
public Object getProxy(ClassLoader classLoader) {
//先獲取 AopProxy,AopProxy有JDK實現(xiàn)和 Cglib實現(xiàn)兩種。
return createAopProxy().getProxy(classLoader);
}
判斷采取JDK代理 或Cglib代理旷坦。
判斷條件:
- Optimize: 是否采用了 激進的優(yōu)化策略掏熬,該優(yōu)化僅支持 Cglib代理
- ProxyTargetClass: 代理目標類,代理目標類 就是采用 子類繼承的方式創(chuàng)建代理秒梅,所以也是Cglib代理旗芬,可以通過。
- hasNoUserSuppliedProxyInterfaces:判斷是否是實現(xiàn)了接口捆蜀,如果沒有必須采用Cglib代理疮丛。
所以如果我們想在項目中 采用Cglib代理的話 application.properties中配置:
spring.aop.proxy-target-class=false幔嫂,或者使用注解配置proxyTargetClass = true .
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
//判斷采用什么代理類型。
//Optimize 是否采用了 激進的優(yōu)化策略誊薄,該優(yōu)化僅支持 Cglib代理
//ProxyTargetClass 代理目標類履恩,代理目標類 就是采用 子類繼承的方式創(chuàng)建代理,所以也是Cglib代理呢蔫,可以通過
// 判斷是否是實現(xiàn)了接口切心,如果沒有必須采用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代理片吊。
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
- 拿JdkDynamicAopProxy來說绽昏。
獲取代理類:
JDK動態(tài)代理的關鍵是 InvocationHandler,JdkDynamicAopProxy實現(xiàn)了InvocationHandler接口定鸟。
@Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
//獲取代理類而涉,其中InvocationHanler 是 this,就是JdkDynamicAopProxy。
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
6.增強方法的執(zhí)行
增強方法的執(zhí)行 是AOP的核心所在联予,理解Spring Aop 是如何 協(xié)調 各種通知 的執(zhí)行啼县,是理解的關鍵。
通知 分為前置 后置 異常 返回 環(huán)繞通知沸久,在一個方法中每種通知的執(zhí)行時機不同季眷,協(xié)調他們之間執(zhí)行順序很重要。
但是Spring AOP 采用了很聰明的方法讓各種各樣的通知準確有序的工作卷胯。
- JdkDynamicAopProxy.invoker
由于invoker 方法很大子刮,我刪除了大部分的代碼,保留幾處關鍵代碼:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//省略了 對 hashCode equals 等方法的處理....
//exposeProxy屬性的支持窑睁。
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// 獲取目標對象類
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// 獲取攔截器鏈 這里this.advised 就是AnnotationAwareAspectJAutoProxyCreator
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
//如果 沒有攔截器鏈 則直接執(zhí)行挺峡。
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
//開始執(zhí)行攔截器鏈
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
//返回執(zhí)行結果。
return retVal;
}
6.1 創(chuàng)建攔截器鏈
攔截器鏈InterceptorAndDynamicMethodMatcher 類 和 MethodInterceptor 類型的集合担钮。
InterceptorAndDynamicMethodMatcher 封裝了 MethodInterceptor 和 MethodMather 橱赠,作用就是在執(zhí)行時進行再次匹配。
創(chuàng)建攔截器的過程就是 對所有 適合 目標類的 Advisor進行再一次篩選箫津。對匹配的Advisor封裝成 MethodInterceptor狭姨。通過MethodInterceptor 統(tǒng)一 增強方法的調用。
6.2 執(zhí)行攔截器鏈
使用ReflectiveMethodInvocation 對連接器鏈進行封裝苏遥。通過process 方法觸發(fā)攔截器開始執(zhí)行饼拍。
public Object proceed() throws Throwable {
// 判斷攔截器鏈 是否執(zhí)行結束,如果執(zhí)行結束執(zhí)行目標方法田炭。
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
//獲取下一個 需要執(zhí)行的 攔截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
//如果需要執(zhí)行時 進行匹配
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
//如果匹配师抄,并不清楚 為什么還要在這里 進行再次匹配。
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments))
//注意這里 將ReflectiveMethodInvocation 自己當參數 教硫,傳入調用司澎。
return dm.interceptor.invoke(this);
}
else {
//遞歸的調用
return proceed();
}
}
else {
//如果 MethodInterceptor
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
查看代碼并沒有發(fā)現(xiàn)任何的關于協(xié)調前置欺缘,后置各種通知的代碼。其實所有的協(xié)調工作都是由MethodInterceptor 自己維護挤安。
- 前置MethodInterceptor(MethodBeforeAdviceInterceptor)的invoke
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//激活 前置增強方法
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
//繼續(xù)調用下一個 攔截器谚殊。
return mi.proceed();
}
-
后置MethodInterceptor(AspectJAfterAdvice)
@Override public Object invoke(MethodInvocation mi) throws Throwable { try { //繼續(xù)調用一下攔截器。 return mi.proceed(); } finally { //在finally 里面激活 后置增強方法 invokeAdviceMethod(getJoinPointMatch(), null, null); } }
通過對比前置 和 后置 攔截器蛤铜,可以發(fā)現(xiàn) 具體協(xié)調各種通知 的 邏輯 實際是利用了 遞歸的思想嫩絮。
后置攔截器為例: 先執(zhí)行 遞歸方法 proceed()。最后從遞歸方法返回的時候 最后調用 后置方法围肥。
7. 模擬攔截器調用
為了更好理解 攔截器調用剿干,自己實現(xiàn)了一個簡單的攔截器鏈調用過程。
7.1 準備工作
-
接口
//方法調用 interface MethodInvocation{ Object process() throws Throwable; } //方法攔截器 interface MethodInterceptor{ Object invoke(MethodInvocation mi) throws Throwable; }
-
實現(xiàn)
//后置增強方法的 攔截器 class AfterMethodInterceptor implements MethodInterceptor{ String identification; public AfterMethodInterceptor(String identification){ this.identification = identification; } @Override public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.process(); }finally { System.out.println("執(zhí)行后置通知"+identification); } } } //前置 的 方法攔截器 class BeforMethodInterceptor implements MethodInterceptor{ String identification; public BeforMethodInterceptor(String identification){ this.identification = identification; } @Override public Object invoke(MethodInvocation mi) throws Throwable { System.out.println("執(zhí)行前置通知"+identification); return mi.process(); } } // 默認的 方法調用實現(xiàn) class DefaultMethodInvacation implements MethodInvocation { List<MethodInterceptor> chian; Object target; //目標對象 Method method; //目標方法 Object[] args; //方法參數 int currentChianIndex; //記錄攔截器鏈當前執(zhí)行位置 public DefaultMethodInvacation(List<MethodInterceptor> chian,Object target,Method method,Object[] args){ this.chian = chian; this.method = method; this.target = target; this.args = args; } @Override public Object process() throws Throwable{ Object value ; //如果 攔截器 執(zhí)行完畢 執(zhí)行 目標方法 if(currentChianIndex == chian.size()){ value = method.invoke(target, args); return value; } //獲取下一個 攔截器 MethodInterceptor methodInterceptor = chian.get(currentChianIndex++); return methodInterceptor.invoke(this); } } //目標對象 class TargetObj{ //目標方法 public void targetMethod(){ System.out.println("-----目標方法執(zhí)行----"); } }
7.2 測試代碼
@Test
public void aopchain() throws Throwable {
List<MethodInterceptor> chain = Lists.newArrayList();
//攔截器鏈穆刻,穿插這 放入 前置 和 后置 攔截器 置尔。
chain.add(new AfterMethodInterceptor("1"));
chain.add(new BeforMethodInterceptor("1"));
chain.add(new AfterMethodInterceptor("2"));
chain.add(new BeforMethodInterceptor("2"));
chain.add(new AfterMethodInterceptor("3"));
chain.add(new BeforMethodInterceptor("3"));
//目標對象
TargetObj targetObj = new TargetObj();
//目標方法
Method method = TargetObj.class.getMethod("targetMethod");
DefaultMethodInvacation defaultMethodInvacation = new DefaultMethodInvacation(chain, targetObj, method, null);
//執(zhí)行攔截器鏈
defaultMethodInvacation.process();
}
7.3 執(zhí)行結果
雖然 前置 和 后置 都是 無序的 存放在 攔截器鏈中,但是 前置 和 后置 都在各自的位置執(zhí)行氢伟。
執(zhí)行前置通知1
執(zhí)行前置通知2
執(zhí)行前置通知3
-----目標方法執(zhí)行----
執(zhí)行后置通知3
執(zhí)行后置通知2
執(zhí)行后置通知1