[Spring] Spring AOP 實現原理剖析(二)

手機用戶請橫屏獲取最佳閱讀體驗,REFERENCES中是本文參考的鏈接,如需要鏈接和更多資源,可以關注其他博客發佈地址。

平臺 地址
CSDN https://blog.csdn.net/sinat_28690417
簡書 https://www.jianshu.com/u/3032cc862300
個人博客 https://yiyuery.github.io/NoteBooks/

[Spring] Spring AOP 實現原理剖析(二)

Spring AOP 增強 Advice

Spring 使用增強類定義橫切邏輯,同時由於Spring方法只支持方法連接點,增強還包括在方法的哪一點加入橫切代碼的方位信息。所以增強包括:1、橫切邏輯;2、部分連接點的信息。

類圖結構

抽象接口 org.springframework.aop

在這裏插入圖片描述

在這裏插入圖片描述

按照增強在目標類方法中的連接點位置,可以分爲以下5類:

  • 前置增強
  • 後置增強
  • 環繞增強
  • 異常拋出增強
  • 引介增強

前四種比較好理解,大致對應於被增強方法的執行時間,前、後、前後、異常拋出四個連接點。最後一種引介增強:IntroductionInterceptor 表示在目標類中添加一些新的方法和屬性。

Advice 增強實戰

前置增強

BeforeAdvice

package org.springframework.aop;

import org.aopalliance.aop.Advice;

/**
 * Common marker interface for before advice, such as {@link MethodBeforeAdvice}.
 *
 * <p>Spring supports only method before advice. Although this is unlikely to change,
 * this API is designed to allow field advice in future if desired.
 *
 * @author Rod Johnson
 * @see AfterAdvice
 */
public interface BeforeAdvice extends Advice {

}

目標:記錄方法執行開始時間

在上一篇《[Spring] Spring AOP 實現原理剖析(一)》的基礎上繼續開展實戰

實現一個前置增強類用於業務類方法執行前的增強。

//方法增強接口(Spring中定義)
public interface MethodBeforeAdvice extends BeforeAdvice {

	/**
	 * Callback before a given method is invoked.
	 * @param method method being invoked
	 * @param args arguments to the method
	 * @param target target of the method invocation. May be {@code null}.
	 * @throws Throwable if this object wishes to abort the call.
	 * Any exception thrown will be returned to the caller if it's
	 * allowed by the method signature. Otherwise the exception
	 * will be wrapped as a runtime exception.
	 */
	void before(Method method, Object[] args, @Nullable Object target) throws Throwable;

}

//增強類
public class BusinessLogHandlerBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        BusinessLogMonitor.begin(method.getName());
    }

}

測試輸出


/**
 * 前置增強
 */
@Test
public void deletePersonWithBeforeAdvice() {

    //創建被增強實例
    PersonManagerServiceImpl personManagerService = new PersonManagerServiceImpl();

    //創建前置增強Advice
    BeforeAdvice beforeAdvice = new BusinessLogHandlerBeforeAdvice();

    //Spring 提供的代理工廠
    ProxyFactory factory = new ProxyFactory();

    //設置代理目標
    factory.setTarget(personManagerService);

    //爲代理目標添加增強
    factory.addAdvice(beforeAdvice);

    //生成代理實例
    PersonManagerServiceImpl proxy = (PersonManagerServiceImpl)factory.getProxy();
    //執行方法
    proxy.deletePerson();
}
//23:44:01.043 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
//23:44:01.065 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除


可以看到在 deletePerson方法執行前,增強邏輯生效了,輸出了

om.example.spring.aop.simple.BusinessLogMonitor - begin monitor...

代理工廠 ProxyFactory

在上一篇《[Spring] Spring AOP 實現原理剖析(一)》曾提到過Spring AOP的底層實現用的還是JDK和CGLib的動態代理技術。

但是我們在使用CGLib時定義了代理類生成的一個輔助構造類

public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    /**
     * 創建代理類
     * @param clazz
     * @return
     */
    public Object createProxy(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        BusinessLogMonitor.begin(obj.getClass().getName()+"."+method.getName());
        Object result = proxy.invokeSuper(obj, args);
        BusinessLogMonitor.end();
        return result;
    }
}

Spring中,也定義了一個AopProxy,並提供了2個實現類:

  • CglibAopProxy

  • JdkDynamicAopProxy

public interface AopProxy {

	/**
	 * Create a new proxy object.
	 * <p>Uses the AopProxy's default class loader (if necessary for proxy creation):
	 * usually, the thread context class loader.
	 * @return the new proxy object (never {@code null})
	 * @see Thread#getContextClassLoader()
	 */
	Object getProxy();

	/**
	 * Create a new proxy object.
	 * <p>Uses the given class loader (if necessary for proxy creation).
	 * {@code null} will simply be passed down and thus lead to the low-level
	 * proxy facility's default, which is usually different from the default chosen
	 * by the AopProxy implementation's {@link #getProxy()} method.
	 * @param classLoader the class loader to create the proxy with
	 * (or {@code null} for the low-level proxy facility's default)
	 * @return the new proxy object (never {@code null})
	 */
	Object getProxy(@Nullable ClassLoader classLoader);

}

從名字可以看出,分別對應兩種AOP實現機制,如果是針對接口進行代理,則使用JdkDynamicAopProxy;如果是針對實例類的代理,則使用CglibAopProxy

相應的,若指定JDK代理技術,需要傳入interfaces參數。另外,ProxyFactory可以通過factory.setOptimize(true); 啓動優化代理方式,這樣,針對接口的代理也會使用CglibAopProxy

Spring 中 xml 配置

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1、被增強類-->
    <bean id="target" class="com.example.spring.aop.service.impl.PersonManagerServiceImpl"/>
    <!--2、增強類-->
    <bean id="businessLogHandlerBeforeAdvice" class="com.example.spring.aop.advice.BusinessLogHandlerBeforeAdvice"/>
    <!--3、代理工廠定義-->
    <!--3.1 指定代理的接口-->
    <!--3.2 指定使用的增強-->
    <!--3.3 指定對哪個bean進行代理-->
    <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="com.example.spring.aop.service.IPersonManagerService"
          p:interceptorNames="businessLogHandlerBeforeAdvice"
          p:target-ref="target"
    />

</beans>

測試輸出

/**
 * Spring XML 配置前置增強
 */
@Test
public void deletePersonWithBeforeAdviceBySpringXML() {
    ApplicationContext context = new ClassPathXmlApplicationContext("config/spring/aop-before-advice.xml");
    IPersonManagerService bean = (IPersonManagerService)context.getBean("personManagerImpl");
    bean.deletePerson();
    log.info("-----------------------");
    bean.modifyPerson(new PersonDO("xx1"));
}
//00:27:02.946 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
//00:27:02.946 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
//00:27:05.951 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImplTest - -----------------------
//00:27:05.951 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
//00:27:05.952 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據修改:xx1

分析:

  • 增強類需要實現接口MethodBeforeAdvice
  • 增強了目標類的所有接口方法
  • 實現了前置增強

後置增強

其實看了前置增強,後置、壞繞對應的實現也大相徑庭,此處就不再追溯,貼了代碼,直接看吧。

後置實現:

public interface AfterAdvice extends Advice {

}

public interface AfterReturningAdvice extends AfterAdvice {

	/**
	 * Callback after a given method successfully returned.
	 * @param returnValue the value returned by the method, if any
	 * @param method method being invoked
	 * @param args arguments to the method
	 * @param target target of the method invocation. May be {@code null}.
	 * @throws Throwable if this object wishes to abort the call.
	 * Any exception thrown will be returned to the caller if it's
	 * allowed by the method signature. Otherwise the exception
	 * will be wrapped as a runtime exception.
	 */
	void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;

}

public class BusinessLogHandlerAfterAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        BusinessLogMonitor.end();
    }
}
/**
 * Spring XML 配置後置增強
 */
@Test
public void deletePersonWithAfterAdviceBySpringXML() {
    ApplicationContext context = new ClassPathXmlApplicationContext("config/spring/aop-after-advice.xml");
    IPersonManagerService bean = (IPersonManagerService)context.getBean("personManagerImpl");
    bean.deletePerson();
    log.info("-----------------------");
    bean.modifyPerson(new PersonDO("xx1"));
}
//00:38:24.737 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
//00:38:27.739 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
//00:38:27.740 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImplTest - -----------------------
//00:38:27.740 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據修改:xx1
//00:38:30.745 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....

分析:

  • 增強類需要實現接口AfterAdvice
  • 增強了目標類的所有接口方法
  • 實現了後置增強

此處順便提下如何配置多個增強:

比如同時配置前置和後置增強:

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1、被增強類-->
    <bean id="target" class="com.example.spring.aop.service.impl.PersonManagerServiceImpl"/>
    <!--2、增強類-->
    <bean id="businessLogHandlerBeforeAdvice" class="com.example.spring.aop.advice.BusinessLogHandlerBeforeAdvice"/>
    <bean id="businessLogHandlerAfterAdvice" class="com.example.spring.aop.advice.BusinessLogHandlerAfterAdvice"/>
    <!--3、代理工廠定義-->
    <!--3.1 指定代理的接口-->
    <!--3.2 指定使用的增強-->
    <!--3.3 指定對哪個bean進行代理-->
    <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="com.example.spring.aop.service.IPersonManagerService"
          p:interceptorNames="businessLogHandlerBeforeAdvice,businessLogHandlerAfterAdvice"
          p:target-ref="target"
    />

</beans>

測試輸出

  /**
     * Spring XML 配置前、後置增強
     */
    @Test
    public void deletePersonWithBeforeAndAfterAdviceBySpringXML() {
        ApplicationContext context = new ClassPathXmlApplicationContext("config/spring/aop-before-and-after-advice.xml");
        IPersonManagerService bean = (IPersonManagerService)context.getBean("personManagerImpl");
        bean.deletePerson();
        log.info("-----------------------");
        bean.modifyPerson(new PersonDO("xx1"));
    }
    //23:28:11.627 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
    //23:28:11.628 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
    //23:28:14.629 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
    //23:28:14.630 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - deletePerson執行耗時3001毫秒
    //23:28:14.630 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImplTest - -----------------------
    //23:28:14.630 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
    //23:28:14.630 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據修改:xx1
    //23:28:17.633 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
    //23:28:17.633 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - modifyPerson執行耗時3003毫秒

分析:

  • 增強了目標類的所有接口方法
  • 實現了前置和後置增強
  • 配置多個增強通過p:interceptorNames="businessLogHandlerBeforeAdvice,businessLogHandlerAfterAdvice"實現。

interceptorNames 是數組形式的參數,接受增強Bean的名稱。也可以採用如下方式配置:

   <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="com.example.spring.aop.service.IPersonManagerService"/>
        <property name="interceptorNames">
            <list>
                <idref bean="businessLogHandlerAfterAdvice"/>
                <idref bean="businessLogHandlerBeforeAdvice"/>
            </list>

        </property>
        <property name="target" ref="target"/>
    </bean>

便於 IDEA 可以自動檢查出錯誤並提示。

壞繞增強

綜合實現前置、後置增強。具體如下:

// 調整編輯方法,使其修改傳入數據後並返回,模擬有數據返回的情況
@Override
public PersonDO modifyPerson(PersonDO personDO) {
    log.info("模擬人員數據修改:"+personDO.getName());
    try {
        Thread.sleep(3000);
    } catch (Exception e) {
        log.error("PersonManagerServiceImpl modifyPerson failed!");
    }
    personDO.setName("被修改_"+personDO.getName());
    return personDO;
}

//增強類實現

public class BusinessLogHandlerAroundAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        BusinessLogMonitor.begin(invocation.getMethod().getName());

        Object object = invocation.proceed();

        BusinessLogMonitor.end();

        return object;
    }
}

Spring配置


<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1、被增強類-->
    <bean id="target" class="com.example.spring.aop.service.impl.PersonManagerServiceImpl"/>
    <!--2、增強類-->
    <bean id="businessLogHandlerAroundAdvice" class="com.example.spring.aop.advice.BusinessLogHandlerAroundAdvice"/>
    <!--3、代理工廠定義-->
    <!--3.1 指定代理的接口-->
    <!--3.2 指定使用的增強-->
    <!--3.3 指定對哪個bean進行代理-->
<!--    <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="com.example.spring.aop.service.IPersonManagerService"
         p:interceptorNames="businessLogHandlerAroundAdvice"
          p:target-ref="target"/>-->

    <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="com.example.spring.aop.service.IPersonManagerService"/>
        <property name="interceptorNames">
            <list>
                <idref bean="businessLogHandlerAroundAdvice"/>
            </list>

        </property>
        <property name="target" ref="target"/>
    </bean>

</beans>

編寫測試類並輸出測試結果:

 /**
     * 環繞增強
     */
    @Test
    public void deletePersonWithAroundAdviceBySpringXML() {
        ApplicationContext context = new ClassPathXmlApplicationContext("config/spring/aop-around-advice.xml");
        IPersonManagerService bean = (IPersonManagerService)context.getBean("personManagerImpl");
        bean.deletePerson();
        log.info("-----------------------");
        PersonDO xx1 = bean.modifyPerson(new PersonDO("xx1"));
        log.info("修改返回結果>>>>"+xx1.getName());
    }
    //00:18:11.646 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
    //00:18:11.646 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
    //00:18:14.650 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
    //00:18:14.651 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - deletePerson執行耗時3005毫秒
    //00:18:14.651 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImplTest - -----------------------
    //00:18:14.651 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
    //00:18:14.651 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據修改:xx1
    //00:18:17.656 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
    //00:18:17.656 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - modifyPerson執行耗時3005毫秒
    //00:18:17.656 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImplTest - 修改返回結果>>>>被修改_xx1

分析

  • 增強類需要實現接口org.aopalliance.intercept.MethodInterceptor
  • 通過invocation.proceed()方法執行前後加入增強邏輯來實現環繞增強
  • 支持返回值
  • 可以實現前置、後置增強的綜合效果

異常拋出增強

異常增強和前幾個增強接口定義不太一樣,僅僅定義了個標籤接口ThrowsAdvice,實際運行時 Spring 根據反射機制自行判斷符合條件的方法簽名,進行增強。

首先,補充個會拋出異常的方法定義:

@Override
public void deleteThrowException() {
    log.info("模擬人員數據刪除,拋出異常");
    try {
        Thread.sleep(3000);
    } catch (Exception e) {
        log.error("PersonManagerServiceImpl deletePerson failed!");
    }
    throw new IllegalArgumentException("刪除失敗,拋出異常");
}

然後,定義增強接口來處理異常:

@Slf4j
public class BusinessLogHandlerThrowExceptionAdvice implements ThrowsAdvice {

    /**
     * ThrowsAdvice 是個標籤接口,運行期 Spring 使用反射機制自行判斷,必須採用簽名形式定義異常拋出的增強方法
     * void afterThrowing(Method method,Object args,Object target,Throwable ex)
     * @param method
     * @param args
     * @param target
     * @param ex
     * @throws Throwable
     */
    public void afterThrowing(Method method,Object args,Object target,Exception ex) throws Throwable{
        log.error("BusinessLogHandlerThrowExceptionAdvice >>> method: "+ method.getName());
    }
}

Spring 配置

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1、被增強類-->
    <bean id="target" class="com.example.spring.aop.service.impl.PersonManagerServiceImpl"/>
    <!--2、增強類-->
    <bean id="businessLogHandlerThrowExceptionAdvice" class="com.example.spring.aop.advice.BusinessLogHandlerThrowExceptionAdvice"/>
    <!--3、代理工廠定義-->
    <!--3.1 指定代理的接口-->
    <!--3.2 指定使用的增強-->
    <!--3.3 指定對哪個bean進行代理-->
<!--    <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="com.example.spring.aop.service.IPersonManagerService"
          p:interceptorNames="businessLogHandlerThrowExceptionAdvice"
          p:target-ref="target"/>-->

    <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="com.example.spring.aop.service.IPersonManagerService"/>
        <property name="interceptorNames">
            <list>
                <idref bean="businessLogHandlerThrowExceptionAdvice"/>
            </list>

        </property>
        <property name="target" ref="target"/>
    </bean>

</beans>

測試輸出

/**
 * 異常增強
 */
@Test
public void deletePersonWithThrowAdviceBySpringXML() {
    ApplicationContext context = new ClassPathXmlApplicationContext("config/spring/aop-throw-exception-advice.xml");
    IPersonManagerService bean = (IPersonManagerService)context.getBean("personManagerImpl");
    bean.deletePerson();
    log.info("-----------------------");
    try {
        bean.deleteThrowException();
    }catch (Exception e){
        log.error("deletePersonWithThrowAdviceBySpringXML catch exception!",e);
    }
}
//23:44:52.021 [main] DEBUG org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor - Found exception handler method on throws advice: public void com.example.spring.aop.advice.BusinessLogHandlerThrowExceptionAdvice.afterThrowing(java.lang.reflect.Method,java.lang.Object,java.lang.Object,java.lang.Exception) throws java.lang.Throwable
//23:44:52.023 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
//23:44:55.027 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImplTest - -----------------------
//23:44:55.027 [main] DEBUG org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor - Found exception handler method on throws advice: public void com.example.spring.aop.advice.BusinessLogHandlerThrowExceptionAdvice.afterThrowing(java.lang.reflect.Method,java.lang.Object,java.lang.Object,java.lang.Exception) throws java.lang.Throwable
//23:44:55.028 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除,拋出異常
//23:44:58.031 [main] ERROR com.example.spring.aop.advice.BusinessLogHandlerThrowExceptionAdvice - BusinessLogHandlerThrowExceptionAdvice >>> method: deleteThrowException
//23:44:58.036 [main] ERROR com.example.spring.aop.service.impl.PersonManagerServiceImplTest - deletePersonWithThrowAdviceBySpringXML catch exception!
//java.lang.IllegalArgumentException: 刪除失敗,拋出異常
//	at com.example.spring.aop.service.impl.PersonManagerServiceImpl.deleteThrowException(PersonManagerServiceImpl.java:74)
//	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
//	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
//	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
//	at java.lang.reflect.Method.invoke(Method.java:498)

分析

  • 實現標記接口
  • 按照約定的方法簽名定義異常增強方法

規則:

  1. 簽名模板: void afterThrowing(Method method,Object args,Object target,Throwable ex)
  2. 前3個入參,要麼提供,要麼不提供
  3. 最後一個入參必須爲Throwable及其子類
  4. 關於異常的匹配規則,在類的繼承樹上,兩個類的距離月近,這相似度越高,匹配時優先選擇相識度高的afterThrowing方法。

引介增強

一種特殊形式增強,它不是在目標方法周圍織入增強,而是爲目標類創建新的方法和屬性,所以引介增強的連接點是類級別的,而非方法級別。

特點

  1. 可以通過引介增強爲目標類添加一個接口的實現。
  2. 可以爲目標類創建實現某接口的代理。

前文,我們對所有方法織入了業務日誌的增強,由於業務日誌的輸出往往會增加系統的負擔,我們可以通過引介增強來實現這個業務日誌輸出的功能開關。

首先,定義一個被增強類需要增強的能力接口,目的是通過其子類實現該接口能力

public interface BusinessLogHandlerIntroduceSwitch {
    /*定義開關*/
    void setSwitch(boolean open);

}

然後,定義增強邏輯,可以加上接口種定義的方法,實現模板方法類型的效果:

/*根據手動設置的開關,判斷業務日誌增強是否開啓*/
public class BusinessLogHandlerIntroduceAdvice extends DelegatingIntroductionInterceptor implements BusinessLogHandlerIntroduceSwitch {

    private ThreadLocal<Boolean> switchMap = new ThreadLocal<>();

    @Override
    public void setSwitch(boolean open) {
        switchMap.set(open);
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object obj = null;
        if (Objects.nonNull(switchMap.get()) && switchMap.get()) {
            BusinessLogMonitor.begin(mi.getMethod().getName());
            obj = super.invoke(mi);
            BusinessLogMonitor.end();
        }else{
            obj = super.invoke(mi);
        }
        return obj;
    }
}

Spring 配置

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1、被增強類-->
    <bean id="personManagerImplTarget" class="com.example.spring.aop.service.impl.PersonManagerServiceImpl"/>
    <!--2、增強類-->
    <bean id="businessLogHandlerIntroduceAdvice" class="com.example.spring.aop.advice.introduce.BusinessLogHandlerIntroduceAdvice"/>
    <!--3、代理工廠定義-->
    <!--3.1 指定代理的接口-->
    <!--3.2 指定使用的增強-->
    <!--3.3 指定對哪個bean進行代理-->
<!--    <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:interfaces="com.example.spring.aop.advice.introduce.BusinessLogHandlerIntroduceSwitch"
          p:interceptorNames="businessLogHandlerIntroduceAdvice"
          p:proxyTargetClass="true"
          p:target-ref="personManagerImplTarget"/>-->

    <bean id="personManagerImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interfaces" value="com.example.spring.aop.advice.introduce.BusinessLogHandlerIntroduceSwitch"/>
        <property name="interceptorNames">
            <list>
                <idref bean="businessLogHandlerIntroduceAdvice"/>
            </list>

        </property>
        <property name="target" ref="personManagerImplTarget"/>
        <!--由於引介增強一定要通過創建子類來生成代理,所以需要強制使用CGLib-->
        <property name="proxyTargetClass" value="true"/>

    </bean>

</beans>

測試

/**
 * 引介增強
 */
@Test
public void deletePersonWithIntroduceAdviceBySpringXML() {
    ApplicationContext context = new ClassPathXmlApplicationContext("config/spring/aop-introduce-advice.xml");
    PersonManagerServiceImpl bean = (PersonManagerServiceImpl)context.getBean("personManagerImpl");
    bean.deletePerson();
    log.info("--------------------------");

    //開啓增強
    BusinessLogHandlerIntroduceSwitch enhancer = (BusinessLogHandlerIntroduceSwitch)bean;
    enhancer.setSwitch(true);

    bean.deletePerson();
}

//00:25:12.774 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
//00:25:15.778 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImplTest - --------------------------
//00:25:15.780 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - begin monitor...
//00:25:15.782 [main] INFO com.example.spring.aop.service.impl.PersonManagerServiceImpl - 模擬人員數據刪除
//00:25:18.783 [main] INFO com.example.spring.aop.simple.BusinessLogMonitor - end monitor....
//00:25:18.784 [main] INFO com.example.spring.aop.simple.BusinessLogHandler - deletePerson執行耗時3002毫秒

分析

  • 和前幾種增強的區別在於,引介增強強制使用CGLib
  • 需要配置被增強類需要實現的增強接口interfaces,而不再是通過JDK通道代理,生成被增強類的代理類
  • 增強類,需要繼承Spring的默認實現DelegatingIntroductionInterceptor,並通過覆蓋父類的invoke方法,結合增強接口,實現自身增強邏輯。
  • 增強類,支持通過模板方法的形式提供增強邏輯的執行流程控制。

總結

本文,通過實戰代碼,詳細介紹了Spring中五種增強方式各自的實現方式。其中,前、後、壞繞、異常增強使用的是JDK動態代理,引介增強強制使用CGLib的方式實現。

下一篇,我們將就切點、切面的控制做進一步的瞭解。

To be continue....

更多

掃碼關注架構探險之道,回覆『源碼』,獲取本文相關源碼和資源鏈接

在這裏插入圖片描述

知識星球(掃碼加入獲取歷史源碼和文章資源鏈接)

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章