摘要:前面介紹的類、接口等都是 Spring AOP 中一些底層 API ,使用起來不太方便,感覺功能不太強大,不論是 ProxyFactory 還是 ProxyFactoryBean 創建織入切面的代理,每次只能硬編碼一個具體的 Bean ,假如我想將某個包路徑下符合一定規則的類的特定方法都進行織入代理怎麼辦。enhancer.setInterfaces() 可用於設置生成的代理類必須實現的接口,比如你可以不設置 superclass ,只設置 interfaces ,這時也是可以正常創建出基於這個接口的動態代理實例,但是這時就要注意不能觸發目標對象方法執行,如 methodProxy.invokeSuper 執行會報錯,如下:。

概述

Spring 的兩大核心: IoCAOPIoC 作爲 Spring 的根基,通過大量的擴展點讓系統輕而易舉的就可以實現良好的擴展性,而 AOPIoC 結合在一起,類似於發生強大化學反應一樣,將 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 中提供了一個工具類: EnhancerSpring 中主要就是利用該工具類創建 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 底層實現中非常重要的一個類,另外 AnnotationAwareAspectJAutoProxyCreatorBeanNameAutoProxyCreatorDefaultAdvisorAutoProxyCreator 等一些高級 AOP 實現工具類都是通過在其父類 AbstractAutoProxyCreator 中藉助 ProxyFactory 實現 AOP 邏輯織入的。所以,理解 ProxyFactory 的使用對理解 Spring AOP 至關重要。

ProxyFactory 類控制代理的創建過程,其內部委託給 DefaultAopProxyFactory 的一個實例,該實例又轉而委託給 Cglib2AopProxyJdkDynamicAopProxy ,用於創建基於 cglib 代理還是 jdk 代理,想了解這兩種動態代理區別可以分析下這個類源碼。

ProxyFactoryaddAdvice() 方法將傳入的通知封裝到 DefaultPointcutAdvisor ( DefaultPointcutAdvisorPointcutAdvisor 的標準實現)的一個實例中,並使用默認包含所有方法的切入點對其進行配置。爲更加靈活細粒度的控制在哪些連接點上攔截通知,可以使用 addAdVisor() 方法添加一個帶有切入點消息的 Advisor

可以使用相同的 ProxyFactory 實例來創建多個代理,每個代理都有不同的切面。爲了幫助實現該過程, ProxyFactory 提供了 removeAdvice()removeAdvisor() 方法,這些方法允許從 ProxyFactory 中刪除之前傳入的任何通知或切面,同時可以使用 boolean adviceIncluded(@Nullable Advice advice) 檢查 ProxyFactory 是否附有特定的通知對象。

Advice

ProxyFactoryaddAdvice()addAdvisor() 兩個方法分別引入了兩個重要的類: AdviceAdvisor 。首先,我們來看下 Advice 這個接口類,其可以看成需要織入增強的代碼邏輯封裝。 AdviceSpringAPI 結構如下:

大致描述:

  • BeforeAdvice
    MethodBeforeAdvice
    @Before
    
  • AfterAdvice
    AfterReturningAdvice
    ThrowsAdvice
    @AfterReturning
    @AfterThrowing
    
  • MethodInterceptor :可以實現環繞通知,對應註解 @Around

Advisor

AOP 規範中有切面概念,在 Spring 中大概對應就是 AdvisorAdvisor 有兩個子接口: PointcutAdvisorIntroductionAdvisor

其實真正使用比較多的是它的子類 PointcutAdvisor ,該接口關鍵就是如下兩個方法:

public interface PointcutAdvisor {
Advice getAdvice();
Pointcut getPointcut();
}

PointcutAdvisor 從接口定義大概就可以看出,其就是對 AdvicePointcut 的封裝, 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 從其定義可以看出其由 ClassFilterMethodMatcher 構成。 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

ProxyFactoryBeanProxyFactory 功能和使用其實差不多,底層邏輯也基本一致, ProxyFactoryBean 主要是融合了 IOC 功能。一方面 ProxyFactoryBean 類是 FactoryBean 的一個實現,更加方便注入 IoC 中,參照 mybatisspring 整合一節,其中關鍵一步就是將掃描的 BeanDefinitionbeanClass 由接口類替換成 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、 AspectJAutoProxyRegistrarImportBeanDefinitionRegistrar 接口實現類:

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 ,理解了如何將 IoCAOP 集成到一起實現強大功能,對 SpringAOP 的整體實現思路也有了比較清晰的認識。

             長按識別關注, 持續輸出原創

相關文章