手機用戶請
橫屏
獲取最佳閱讀體驗,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)
分析
- 實現標記接口
- 按照約定的方法簽名定義異常增強方法
規則:
- 簽名模板: void afterThrowing(Method method,Object args,Object target,Throwable ex)
- 前3個入參,要麼提供,要麼不提供
- 最後一個入參必須爲Throwable及其子類
- 關於異常的匹配規則,在類的繼承樹上,兩個類的距離月近,這相似度越高,匹配時優先選擇相識度高的
afterThrowing
方法。
引介增強
一種特殊形式增強,它不是在目標方法周圍織入增強,而是爲目標類創建新的方法和屬性,所以引介增強的連接點是類級別的,而非方法級別。
特點
- 可以通過引介增強爲目標類添加一個接口的實現。
- 可以爲目標類創建實現某接口的代理。
前文,我們對所有方法織入了業務日誌的增強,由於業務日誌的輸出往往會增加系統的負擔,我們可以通過引介增強來實現這個業務日誌輸出的功能開關。
首先,定義一個被增強類需要增強的能力接口,目的是通過其子類實現該接口能力
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....
更多
掃碼關注
架構探險之道
,回覆『源碼』,獲取本文相關源碼和資源鏈接
知識星球(掃碼加入獲取歷史源碼和文章資源鏈接)