上一篇:Spring學習筆記(六、Spring AOP基本概念)
一、Spring AOP API
- 這是Spring 1.2歷史用法,現在(V4.0)仍然支持。
- 這是Spring AOP 基礎,不得不了解。
- 現在的用法也是基于歷史的,只是更簡便了。
1. Pointcut
- 實現之一:NameMatchMethodPointcut,根據方法名字進行匹配。
- 成員變量:mappedNames,匹配的方法名集合。
創建BizLogic接口:
package test16;
/**
* Created by amber on 2017/6/19.
*/
public interface BizLogic {
String save();
}
創建實現類BizLogincImpl :
package test16;
/**
* Created by amber on 2017/6/19.
*/
public class BizLogincImpl implements BizLogic {
public String save() {
System.out.println("BizLogincImpl:save");
return "BizLogincImpl:save";
}
}
applicationContext:
<bean id="bizLogincTarget" class="test16.BizLogincImpl"></bean>
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sa*</value>
</list>
</property>
</bean>
2. Before advice
- 一個簡單的通知類型
- 只是在進入方法之前被調用,不需要MethodInvocation對象
- 前置通知可以在連接點執行之前插入自定義行為,但不能改變返回值。
創建前置通知類BeforeAdvice :
package test16;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* Created by amber on 2017/6/19.
*/
public class BeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("我是前置通知,攔截的方法名:" + method.getName() + " 目標類名:" + target.getClass().getName());
}
}
3. Throws advice
- 如果連接點拋出異常,throws advice 在連接點返回后被調用。
- 如果throws-advice的方法拋出異常,那么它將覆蓋原有異常
- 接口org.springframework.aop.ThrowsAdvice 不包含任何方法,僅僅是一個聲明,實現類需要實現類似下面的方法:
void afterThrowing([Method,args,target],ThrowableSubclass);
如:
public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException ex)
public void afterThrowing(Method method,Object[] args,Object target,Exception ex)
public void afterThrowing(Method method,Object[] args,Object target,ServletException ex)
創建MyThrowsAdvice類:
package test16;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
/**
* Created by amber on 2017/6/19.
*/
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex) {
System.out.println("拋出異常通知: afterThrowing 1");
}
public void afterThrowing(Method method, Object[] objects, Object target, Exception ex) {
System.out.println("拋出異常通知: afterThrowing 2,出現異常的方法名:" + method.getName() + " 出現異常的類名:" + target.getClass().getName());
}
}
4. After Returning advice
- 后置通知必須實現org.springframework.aop.AfterReturningAdive接口
- 可以訪問返回值(但不能進行修改),被調用的方法,方法的參數和目標。
- 如果拋出異常,將會拋出攔截器鏈,替代返回值。
創建AfterReturningAdvice 類:
package test16;
import java.lang.reflect.Method;
/**
* Created by amber on 2017/6/19.
*/
public class AfterReturningAdvice implements org.springframework.aop.AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("我是返回后通知, 攔截的方法名:"+method.getName()+" 目標類名:"+target.getClass().getName()+" 返回的值:"+returnValue );
}
}
5. Interception around advice
- Spring的切入點模型使得切入點可以獨立與advice重用,以針對不同的advice可以使用相同的切入點。
創建MethodInterception 類:
package test16;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* Created by amber on 2017/6/19.
*/
public class MethodInterception implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("MethodInterceptor(類似環繞通知) invoke 1,攔截的方法: "+invocation.getMethod().getName()+" 攔截的目標類名:"+invocation.getStaticPart().getClass().getName());
Object obj=invocation.proceed();
System.out.println("MethodInterceptor(類似環繞通知) invoke 2,攔截的目標: "+obj);
return obj;
}
}
6. Introduction advice
- 引用通知概念:一個Java類,沒有實現A接口,在不修改Java類的情況下,使其具備A接口的功能。
- Spring 把引入通知作為一種特殊的攔截通知
- 需要IntroductionAdvisor和IntroductionInterceptor
- 僅適用于類,不能和任何切入點一起使用
- 一個Spring test suite例子
- 如果調用lock()方法,希望所有的setter方法拋出LockedException異常(如使物體不變,Spring典型例子)
- 需要一個完成繁重任務的IntroductionInterceptor,這種情況下,可以使用org.springframework.aop.support.DelegatingIntroducationInterceptor。
有時間再做實驗!!!
7. Advisor API in Spring
- Advisor是僅包含一個切入點表達式關聯的單個通知的方面。
- 除了introductions,advisor可以用于任何通知。
- org.springframework.aop.support.DefaultPointcutAdvisor是最常用的類,它可以與MethodInterceptor,BeforeAdvice或者ThrowsAdvice一起使用。
- 它可以混合在Spring同一個AOP代理的advisor和advice。
二、ProxyFactoryBean
- 創建Spring AOP代理的基本方法是使用org.apringframework.aop.framework.ProxyFactoryBean。
- 這個類可以完全控制切入點和通知(advice)以及它們的順序。
- 使用ProxyFactoryBean或者其他IoC相關類,來創建AOP代理的最重要好處是:通知和切入點也可以由IoC管理。
- 被代理類沒有實現任何接口,使用CGLIB代理,否則JDK代理。
- 通過設置proxyTargetClass為true,可強制使用CGLIB
- 如果目標類實現了一個(或者多個)接口,那么創建代理的類型將依賴ProxyFactoryBean 的配置。
- 如果ProxyFactoryBean的proxyInterfaces屬性被設置為一個或者多個全限定接口名,基于JDK的代理將被創建。
- 如果ProxyFactoryBean的proxyInterfaces屬性沒有被設置,但是目標類實現了一個(或者更多)接口,那么ProxyFactoryBean將自動檢測到這個目標類已經實現了至少一個接口,創建一個基于JDK的代理。
關于CGLIB動態代理,參考:CGLib動態代理原理及實現
關于JDK動態代理,參考:JDK動態代理實現原理
1. Proxying interfaces
- 使用PointcutBean,不指定proxyInterfaces:
- applicationContext中:
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="bizLogincTarget" class="test16.BizLogincImpl"></bean>
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sa*</value>
</list>
</property>
</bean>
<bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="beforeAdvice"/>
<property name="pointcut" ref="pointcutBean"/>
</bean>
<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="bizLogincTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>defaultAdvisor</value>
<value>afterReturningAdvice</value>
<value>methodInterception</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
- 測試類:
@Test
public void test16() {
BizLogic logic=super.getBean("bizLogicImpl");
logic.save();
}
- 結果:
Paste_Image.png
- 不使用PointcutBean,指定proxyInterfaces:
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>test16.BizLogic</value>
</property>
<property name="target">
<ref bean="bizLogicTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterReturningAdvice</value>
<value>methodInterception</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
結果:
Paste_Image.png
- 可以使用匿名內部bean來隱藏目標和代理之前的區別
<property name="target">
<ref bean="bizLogicTarget"/>
</property>
引用beanId替換成直接引用路徑。
<property name="target">
<bean class="test16.BizLogincImpl"/>
</property>
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>test16.BizLogic</value>
</property>
<property name="target">
<bean class="test16.BizLogincImpl"/>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterReturningAdvice</value>
<value>methodInterception</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
結果:
Paste_Image.png
三、Proxing classes
- 前面的例子如果沒有接口,這種情況下Spring會使用CGLIB代理,而不是JDK動態代理。
- 如果想,可以強制在任何情況下使用CGLIB,即使有接口。
- CGLIB代理的工作原理是在運行時生成目標類的子類,Spring配置這個生成的子類委托方法調用到原來的目標。
- 子類是用來實現Decorator模式,織入通知。
1. CGLIB的代理對用戶來說是透明的,需要注意:
- final方法不能被通知,因為他們不能覆蓋。
- 不用把CGLIB添加到classpath中,在Spring3.2中,CGLIB被重新包裝并包含在Spring核心的JAR(即基于CGLIB的AOP就像JDK動態代理一樣“開箱即用”)
2. 使用global advisors
- 用*做通配,匹配所有攔截器加入通知鏈。
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="bizLogicTarget" class="test16.BizLogincImpl"></bean>
<bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>test16.BizLogic</value>
</property>
<property name="target">
<bean class="test16.BizLogincImpl"/>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterReturningAdvice</value>
<value>method*</value>//只適用于實現了MethodInterceptor接口的攔截器,其他advice不適用哦!!!
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
3. 簡化的Proxy定義
- 使用父子bean定義,以及內部bean定義,可能會帶來更清潔更更簡潔的代理定義(抽象屬性標記父bean定義為抽象的這樣它不能被實例化)
修改applicationContext:
<bean id="beforeAdvice" class="test16.BeforeAdvice"/>
<bean id="afterReturningAdvice" class="test16.AfterReturningAdvice"/>
<bean id="methodInterception" class="test16.MethodInterception"/>
<bean id="myThrowsAdvice" class="test16.MyThrowsAdvice"/>
<bean id="baseProxyBean" class="org.springframework.aop.framework.ProxyFactoryBean" lazy-init="true" abstract="true"/>
<bean id="bizLogicImpl" parent="baseProxyBean">//將baseProxyBean作為父類
<property name="target">
<bean class="test16.BizLogincImpl"/>//直接引用實現類
</property>
<property name="proxyInterfaces">
<value>test16.BizLogic</value>
</property>
<property name="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterReturningAdvice</value>
<value>methodInterception</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
結果:
Paste_Image.png
4. 使用ProxyFactory
- 使用Spring AOP而不依賴于Spring IoC。
- 大多數情況下,最佳實踐是用IoC容器創建AOP代理。
-
雖然可以硬編碼方式實現,但是Spring推薦使用配置或注解方式實現。
Paste_Image.png
5. 使用auto-proxy
- Spring也允許使用“自動代理”的bean定義,它可以自動代理選定的bean,這是建立在Spring的“bean post processer”功能基礎之上的(在加載bean的時候可以修改)。
- BeanNameAutoProxyCreator。
Paste_Image.png
- DefaultAdvisorAutoProxyCreator,當前IoC容器中自動應用,不用顯示聲明引用advisor的bean定義。
Paste_Image.png