Spring 源碼(十):Spring AOP 核心 API
摘要:前面介紹的類、接口等都是 Spring AOP 中一些底層 API ,使用起來不太方便,感覺功能不太強大,不論是 ProxyFactory 還是 ProxyFactoryBean 創建織入切面的代理,每次只能硬編碼一個具體的 Bean ,假如我想將某個包路徑下符合一定規則的類的特定方法都進行織入代理怎麼辦。enhancer.setInterfaces() 可用於設置生成的代理類必須實現的接口,比如你可以不設置 superclass ,只設置 interfaces ,這時也是可以正常創建出基於這個接口的動態代理實例,但是這時就要注意不能觸發目標對象方法執行,如 methodProxy.invokeSuper 執行會報錯,如下:。
概述
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 @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;
@Component
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("-----洗手-----");
}
}
@Test
public 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、定義一個目標類,後續就是基於該類進行增強:
@Component
public 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
的整體實現思路也有了比較清晰的認識。
長按識別關注, 持續輸出原創