概述
Spring
的兩大核心:IoC
和AOP
,IoC
作為Spring
的根基,通過大量的擴展點讓系統輕而易舉的就可以實現良好的擴展性,而AOP
和IoC
結合在一起,類似于發生強大化學反應一樣,將Spring
的功能性又提高了一個層次。Spring
中也有大量使用AOP
場景,比如@Configuration
、數據庫事務、mybatis mapper
接口注入等等。
(資料圖片)
AOP
全稱Aspect Oriented Programming
,即面向切面編程,其并非Spring
獨有,作為一種對OOP
編程思想的補充,其也有自己的標準規范并有獨立的組織進行維護。
根據織入時機的不同,AOP
又可以分為三類:
ApectJ
主要采用的就是編譯時織入方式,這種一般使用特定的編譯器方式實現;類加載時織入:這種一般都是依賴JVM Instruments
技術實現,Spring中也有對這種技術支持,具體可以了解下LoadTimeWeaver
;動態織入:動態代理方式實現AOP
就是動態織入場景,Spring
中實現AOP
最主要方式,根據動態代理方式不同,又可以分為:JDK動態代理
或CGLIB動態代理
。AOP
標準規范是由獨立的組織機構進行維護,其涉及到的核心概念主要如下:
JoinPoint
):程序運行中的某個階段點,比如方法的調用、異常的拋出、類初始化和對象實例化等。連接點是AOP
的核心概念,并且定義了在應用程序中可以使用AOP
插入其它邏輯的點;切點(Pointcut
):切點是基于規則定義如何查找連接點,其可以看成包含一系列連接點的組合,Spring
中對應的是Pointcut
接口,定義了哪些類的哪些方法需要織入增強;通知(Advice
):在連接點處需要織入的增強代碼邏輯封裝;切面(Aspect
):切面是Advice
和Pointcut
組合,對應Spring
中Advisor
;織入(Weaving
):織入是在適當的位置將切面插入到應用程序代碼中的過程,就是上面說的編譯時織入、類加載時織入和動態織入;目標對象(target
):AOP
代理增強的原生對象;基礎API
Spring AOP
很多人不能很好的理解、使用,一方面是因為AOP
涉及的概念可能比較抽象,不容易理解;另外一方面你對Spring AOP
涉及到的一些基礎API
不熟悉。下面我們就對Spring AOP
中最核心的一些API
,由底向上,由基礎到高級方式一步步分析。
Enhancer
Spring AOP
主要使用的是動態代理方式實現,動態代理實現主要包括兩種:jdk動態代理
和cglib動態代理
。jdk動態代理
方式比較熟悉,下面就來看下cglib動態代理
如何實現。
Spring
中提供了一個工具類:Enhancer
,Spring
中主要就是利用該工具類創建cglib動態代理
。下面我們通過一個案例看下其基本使用:
1、創建Callback
回調接口類,該接口中就可以實現增強邏輯:
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { try { before(method);//前置通知 Object ret = methodProxy.invokeSuper(obj, args);//目標方法執行 after(method, ret);//后置通知 return ret; } catch (Exception e) { exception();//異常通知 } finally { afterReturning();//方法返回通知 } return null; } //前置增強 private void before(Method method) { System.out.printf("before execute:%s\r\n", method.getName()); } //后置增強 private void after(Method method, Object ret) { System.out.printf("after execute:%s, ret:%s\r\n", method.getName(), ret); } //異常增強 private void exception() { System.out.println("execute failure"); } //after返回增強 private void afterReturning() { System.out.println("execute finish"); } }
2、編寫測試:
//NoOp.INSTANCE:NoOp回調把對方法調用直接委派給這個方法在父類中的實現,即可理解為真實對象直接調用方法,沒有任何增強private static final Callback[] CALLBACKS = new Callback[] { new MyMethodInterceptor(), NoOp.INSTANCE};public void test() { //創建Enhancer實例 Enhancer enhancer = new Enhancer(); //cglib是基于繼承方式代理,superClass就是基于哪個類型父類進行增強,創建出來的對象就是該類型子類 enhancer.setSuperclass(UserServiceImpl.class); //CallbackFilter主要用于過濾不同Method使用不同的Callback enhancer.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { if (method.getDeclaringClass() == Object.class) { return 1;//使用Callback數組下標是1的 } return 0;//使用Callback數組下標是0的 } }); //設置Callback數組,Callback就是封裝的增強邏輯 enhancer.setCallbacks(CALLBACKS); //創建代理對象 UserService proxyObj = (UserService) enhancer.create(); System.out.println(proxyObj.say("zhangsan"));}
通過上面enhancer.create()
這條語句,就可以為目標類創建一個cglib動態代理
,通過Callback
回調方式將各種增強邏輯織入到代理實例中。
還可以使用
Enhancer.createClass()
方法只創建出代理類型,然后自己通過反射方式創建對象。這時,需要注意:1、這時就不能使用setCallbacks()
設置Callback
數組,而是使用setCallbackTypes()
設置Callback
對應的類型;2、Enhancer.createClass()
執行完成后,再通過Enhancer.registerStaticCallbacks(clz, CALLBACKS)
方式設置Callback
數組;
enhancer.setInterfaces()
可用于設置生成的代理類必須實現的接口,比如你可以不設置superclass
,只設置interfaces
,這時也是可以正常創建出基于這個接口的動態代理實例,但是這時就要注意不能觸發目標對象方法執行,如methodProxy.invokeSuper
執行會報錯,如下:
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { try { before(method);//前置通知 //Object ret = methodProxy.invokeSuper(obj, args);//目標方法執行 after(method, ret);//后置通知 return ret; } catch (Exception e) { exception();//異常通知 } finally { afterReturning();//方法返回通知 } return null;}
基于接口創建的代理實例還是非常有用的,比如mybatis mapper
就是一個沒有實現類的接口,但是在spring
中卻可以依賴注入到service bean
中,其中就是利用到上面基于接口創建動態代理的思想,注入進來的其實就是基于接口的動態代理,然后調用接口中方法時就可以進行攔截,獲取到具體調用方法簽名信息以及參數信息,基于這些數據進行業務邏輯處理。
invoke和invokeSuper方法區別
Callback#intercept()
回調方法中執行methodProxy.invokeSuper()
和methodProxy.invoke()
是有很大區別的,而且看不到執行流程,所以這里涉及的邏輯非常繞。
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object ret = methodProxy.invokeSuper(obj, args); Object ret = methodProxy.invoke(delete, args);}
大致區別如下圖:
客戶端觸發代理對象say
方法調用,首先進入代理對象中的同名方法,然后進入方法攔截對象MethodInterceptor
,這里會出現兩種情況:
invokeSuper
方法,流程會重新走到代理對象中,代理對象這時會識別出是調用super
中同名方法,所以沒有繼續向下走,而是通過super.say()
方式調用目標對象中的方法。如果調用invoke
方法,代理對象內部其實包裹一個目標對象target
,這時它是直接通過target.say
方式調用目標對象。invokeSuper()
和invoke()
方法都可以調用到目標對象方法,但是它們之間存在的一個本質區別:上下文環境不一樣;或者更直接說:目標對象中this
指向不一樣。通過super.say()
方式調用的目標對象,this
指向的是代理對象;而通過target.say()
方式調用的,目標對象中this
指向的就是目標對象本身。這會導致什么差異呢?
假如目標對象類型如下定義,然后使用Enhancer
創建一個代理對象:
public class Target { public void a() { System.out.println(" a 方法"); b(); } public void b() { System.out.println(" b 方法"); }}
客戶端觸發代理對象a()
方法執行,如果攔截器中使用invoke
方式調用目標對象:直接調用目標對象a()
方法,這個方法中又會通過this.b()
調用方法b
,由于是目標對象本身內部調用,所以b()
方法不會被攔截的。
客戶端觸發代理對象a()
方法執行,如果攔截器中使用invokeSuper()
方式調用目標對象:這里是通過super.a()
方式調用目標對象中的a()
方法,然后a()
方法又會通過this.b()
調用方法b
,注意這時的this
不是目標對象本身,而是代理對象,因為代理對象繼承目標對象,代理對象會有重名方法覆寫了目標對象方法。所以,this.b()
實際上會觸發代理對象中方法b
的執行,這時是會觸發攔截器的。
所以,methodProxy.invokeSuper(obj, args)
這個obj
是代理對象;而methodProxy.invoke(obj, args)
這個入參obj
是目標對象。搞清楚這些基本理解清楚應該使用invoke
還是invokeSuper
。
ProxyFactory
Enhancer
只能用于創建cglib動態代理
,Spring
中還有一個更上層點的類ProxyFactory
,可以用于創建JDK動態代理
或cglib動態代理
。
public void test02() { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(new UserServiceImpl()); /* 調用ProxyFactory.addInterface()或setInterfaces()表示對接口進行代理,一般會使用jdk動態代理, 除非setOptimize(true)或setProxyTargetClass(true)表示使用cglib代理 cglib代理類名稱格式大致為:ServiceImpl$$EnhancerBySpringCGLIB$$f2952b94 */ //setInterfaces設置這個,會基于接口代理,使用jdk動態代理方式 proxyFactory.setInterfaces(UserService.class); //proxyFactory.setOptimize(true); //proxyTargetClass=true:直接代理目標類,而不是接口,使用cglib proxyFactory.setProxyTargetClass(true); // 添加增強 proxyFactory.addAdvice(new MyBeforeAdvice02()); //內部使用了緩存,target不變時,getProxy()獲取到的都是同一個 //只有target變化時,才會重新創建新的代理對象 Object proxy = proxyFactory.getProxy();}
ProxyFactory
類是AOP
底層實現中非常重要的一個類,另外AnnotationAwareAspectJAutoProxyCreator
、BeanNameAutoProxyCreator
、DefaultAdvisorAutoProxyCreator
等一些高級AOP
實現工具類都是通過在其父類AbstractAutoProxyCreator
中借助ProxyFactory
實現AOP
邏輯織入的。所以,理解ProxyFactory
的使用對理解Spring AOP
至關重要。
ProxyFactory
類控制代理的創建過程,其內部委托給DefaultAopProxyFactory
的一個實例,該實例又轉而委托給Cglib2AopProxy
或JdkDynamicAopProxy
,用于創建基于cglib
代理還是jdk
代理,想了解這兩種動態代理區別可以分析下這個類源碼。
ProxyFactory
類addAdvice()
方法將傳入的通知封裝到DefaultPointcutAdvisor
(DefaultPointcutAdvisor
是PointcutAdvisor
的標準實現)的一個實例中,并使用默認包含所有方法的切入點對其進行配置。為更加靈活細粒度的控制在哪些連接點上攔截通知,可以使用addAdVisor()
方法添加一個帶有切入點消息的Advisor
。
可以使用相同的ProxyFactory
實例來創建多個代理,每個代理都有不同的切面。為了幫助實現該過程,ProxyFactory
提供了removeAdvice()
和removeAdvisor()
方法,這些方法允許從ProxyFactory
中刪除之前傳入的任何通知或切面,同時可以使用boolean adviceIncluded(@Nullable Advice advice)
檢查ProxyFactory
是否附有特定的通知對象。
Advice
ProxyFactory
類addAdvice()
和addAdvisor()
兩個方法分別引入了兩個重要的類:Advice
和Advisor
。首先,我們來看下Advice
這個接口類,其可以看成需要織入增強的代碼邏輯封裝。Advice
在Spring
中API
結構如下:
大致描述:
BeforeAdvice
:前置通知,該接口沒有任何方法,平時主要使用其子類MethodBeforeAdvice
,因為Spring中只能在方法前后織入,對應注解@Before
;AfterAdvice
:后置通知,其存在兩個子類AfterReturningAdvice
和ThrowsAdvice
,分別對應@AfterReturning
和@AfterThrowing
;MethodInterceptor
:可以實現環繞通知,對應注解@Around
;Advisor
在AOP
規范中有切面概念,在Spring
中大概對應就是Advisor
。Advisor
有兩個子接口:PointcutAdvisor
和IntroductionAdvisor
:
其實真正使用比較多的是它的子類PointcutAdvisor
,該接口關鍵就是如下兩個方法:
public interface PointcutAdvisor { Advice getAdvice(); Pointcut getPointcut();}
PointcutAdvisor
從接口定義大概就可以看出,其就是對Advice
和Pointcut
的封裝,Advice
代表的是橫切面需要織入的代碼,而Pointcut
定義了如何去切的問題。從之前分析來看,Advice
也可以看出一種非常簡單的切面,是對指定的類所有方法都進行切入,橫切面太寬泛,靈活性不夠,PointAdvisor
引入了Pointcut
后顯然比Advice
更加靈活、強大。
PointcutAdvisor
主要有6個具體的實現類,分別介紹如下:
DefaultPointcutAdvisor
:最常用的切面類型,它可以通過任意Pointcut
和Advice
定義一個切面,一般可以通過擴展該類實現自定義的切面;NameMatchMethodPointcutAdvisor
:通過該類可以定義按方法名定義切點的切面;RegexpMethodPointcutAdvisor
:使用正則表達式模式定義切點,其內部通過JdkRegexpMethodPointcut
構造出正則表達式方法名切點;StaticMethodMatcherPointcutAdvisor
:靜態方法匹配器切點定義的切面,默認情況下,匹配所有的目標類;AspecJExpressionPointcutAdvisor
:用于Aspecj
切點表達式定義切點的切面;AspecJPointcutAdvisor
:用于AspecJ
語法定義切點的切面;其實,這些Advisor
主要區別還是基于其內部封裝的Pointcut
實現類體現的,在實際工作中這些類使用的可能不多,這里的核心在于Pointcut
如何定義切入點,所以實際開發中更多的可能會去定制Pointcut
實現類,然后使用DefaultPointcutAdvisor
將其包裝成Advisor
使用。
下面通過RegexpMethodPointcutAdvisor
案例簡單了解即可:
public void test1(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("aop.demo03"); UserServiceImpl target = new UserServiceImpl(); ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTarget(target); proxyFactoryBean.setProxyTargetClass(true); RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(); //設置advisor的advice advisor.setAdvice(new MyBeforeAdvice02()); //設置advisor的pointcut,aop.demo03包下所有類中已say開頭的方法才會織入 advisor.setPattern("aop.demo03..*.say*"); proxyFactoryBean.addAdvisor(advisor); proxyFactoryBean.setBeanFactory(context); Object obj = proxyFactoryBean.getObject(); System.out.println(obj.getClass().getName()); UserServiceImpl userService = (UserServiceImpl) obj; System.out.println(userService.say("haha"));}
RegexpMethodPointcutAdvisor
表示通過正則表達式進行切點描述的切面,它有一個pattern
屬性用來指定增強要應用到哪些類的哪些方法,也可以通過patterns
屬性指定多個表達式進行匹配。有一個advice
屬性用來表示要應用的增強,這樣就能表示一個完整的切面了。
Pointcut
Advisor
引入了一個核心接口Pointcut
,其描述了對哪些類的哪些方法進行切入。Pointcut
從其定義可以看出其由ClassFilter
和MethodMatcher
構成。ClassFilter
用于定位哪些類可以進行切入,然后再通過MethodMatcher
定位類上的哪些方法可以進行切入,這樣Pointcut
就擁有了識別哪些類的哪些方法能被進行切入的能力。
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher();}
Pointcut
接口API
結構見下:
其中這里比較常用的AnnotationMatchingPointcut
基于注解進行切入,之前分析【Spring源碼】- 09 擴展點之@Import注解一節就使用到該實現類,將標記有@MyAsync
注解的方法都進行增強就是利用這個實現類:
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MyAsync.class);Advice advice = new AsyncAnnotationAdvice(executor);DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();advisor.setPointcut(pointcut);advisor.setAdvice(advice);ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTarget(bean);if(!this.isProxyTargetClass()){ proxyFactory.setInterfaces(bean.getClass().getInterfaces());}proxyFactory.addAdvisor(advisor);proxyFactory.copyFrom(this);return proxyFactory.getProxy();
ProxyFactoryBean
ProxyFactoryBean
和ProxyFactory
功能和使用其實差不多,底層邏輯也基本一致,ProxyFactoryBean
主要是融合了IOC
功能。一方面ProxyFactoryBean
類是FactoryBean
的一個實現,更加方便注入IoC
中,參照mybatis
與spring
整合一節,其中關鍵一步就是將掃描的BeanDefinition
中beanClass
由接口類替換成FactoryBean
類型;另一點就是切面可以使用IoC
容器bean
。
下面通過一個案例簡單看下ProxyFactoryBean
使用:
package org.simon.ioc.demo1;import org.springframework.aop.MethodBeforeAdvice;import org.springframework.stereotype.Component;import java.lang.reflect.Method;@Componentpublic class MyBeforeAdvice implements MethodBeforeAdvice { public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("-----洗手-----"); }}
@Testpublic void test1(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("org.simon.ioc.demo1"); UserService target = new UserServiceImpl(); ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTarget(target); proxyFactoryBean.setInterceptorNames("myBeforeAdvice", "otherAdvice*"); proxyFactoryBean.setBeanFactory(context); Object obj = proxyFactoryBean.getObject(); System.out.println(obj.getClass().getName()); UserService userService = (UserService) obj; System.out.println(userService.say("haha"));}
其中關鍵一步在:proxyFactoryBean.setInterceptorNames("myBeforeAdvice", "otherAdvice*");
,可以使用IoC
中的beanName
,同時還支持通配符*
從IoC
中查找對應Bean
。
高級API
前面介紹的類、接口等都是Spring AOP
中一些底層API
,使用起來不太方便,感覺功能不太強大,不論是ProxyFactory
還是ProxyFactoryBean
創建織入切面的代理,每次只能硬編碼一個具體的Bean
,假如我想將某個包路徑下符合一定規則的類的特定方法都進行織入代理怎么辦?
使用前面那些API
好像都不能實現這個需求,但是結合之前分析的Spring
擴展點,很容易想到可以結合BeanPostProcessor
擴展點實現這個需求,postProcessAfterInitialization()
這個方法回調時Bean
依賴注入、初始化等都已經完成,這時就可以在這個方法中過濾出符合一定條件的Bean
進行代理增強處理。
其實,在Spring
中基于這種思想,已經為我們提供了三個實現類:
BeanNameAutoProxyCreator
:基于beanName
進行織入增強,通過setBeanNames(String... beanNames)
將需要增強的beanName
設置進去;DefaultAdvisorAutoProxyCreator
:基于Advisor
匹配機制進行織入增強,它會對容器中所有的Advisor
進行掃描,自動將這些切面應用到匹配的Bean
中;AnnotationAwareAspectjAutoProxyCreator
:基于Bean
中AspectJ
注解方式進行織入增強,就是實現平時使用的@Aspect
、@Before
、@Around
等AspectJ
注解功能支持。下面我們就來通過DefaultAdvisorAutoProxyCreator
了解下使用場景:
1、定義一個目標類,后續就是基于該類進行增強:
@Componentpublic class UserServiceImpl{ public String say(String name){ System.out.println("執行:==UserService#say==="); return "hello,"+name; }}
2、定義一個配置類:
@Configuration@ComponentScan(basePackageClasses = AopConfig.class)public class AopConfig { @Bean public RegexpMethodPointcutAdvisor regexpMethodPointcutAdvisor(){ RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(); advisor.setAdvice(new MyBeforeAdvice02()); //aop.demo03包下所有類中帶有say開頭方法進行增強 advisor.setPattern("aop.demo03..*.say*"); return advisor; } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ return new DefaultAdvisorAutoProxyCreator(); }}
這個類有兩個關鍵,一個是向IoC
中注入Advisor
,之前分析過Advisor
包含兩個功能:
Pointcut
定位哪些類的哪些方法需求切入;通過關聯的Advice
指定切入增強邏輯;另一個關鍵就是注入DefaultAdvisorAutoProxyCreator
,這個就是一個Spring
內置的實現BeanPostProcessor
擴展類,其在postProcessAfterInitialization()
方法中對Bean
進行切入增強。
3、測試:
public void test1(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); System.out.println(context.getBean(UserServiceImpl.class).getClass());}
context.getBean(UserServiceImpl.class)
從IoC
容器中獲取的就是使用cglib
代理后的實例。
下面我們再來分析下AnnotationAwareAspectjAutoProxyCreator
,平時如果項目中需要開啟AOP
功能,使用@EnableAspectJAutoProxy
注解方式開啟,我們來看下該注解干了什么?
1、@EnableAspectJAutoProxy
注解使用@Import
注解將AspectJAutoProxyRegistrar
引入:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(AspectJAutoProxyRegistrar.class)public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false;}
2、AspectJAutoProxyRegistrar
是ImportBeanDefinitionRegistrar
接口實現類:
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //注冊一個SmartInstantiationAwareBeanPostProcessor類型的實現類:AnnotationAwareAspectJAutoProxyCreator AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } }}
其中最關鍵一句AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
,就是向IoC容器中注入AnnotationAwareAspectJAutoProxyCreator
。
這樣我們就明白了@EnableAspectJAutoProxy
注解方式開啟AOP
的本質就像向IoC
中注入AnnotationAwareAspectJAutoProxyCreator
,它利用BeanPostProcessor
擴展點功能實現織入增強邏輯。
總結
首先,對Spring AOP
底層一些最基礎、最核心的API
的分析梳理,相信你會對Spring AOP
底層實現邏輯有了一個更加深入的理解。然后通過Spring AOP
提供的高級API
,理解了如何將IoC
和AOP
集成到一起實現強大功能,對Spring
中AOP
的整體實現思路也有了比較清晰的認識。
關鍵詞:
責任編輯:Rex_28