
Advice生命周期
每個Advice都是一個Bean。Advice實例可以在所有Advisor之間共享,也可以對每個Advisor對象都是唯一的。這對應于每個類或每個實例的Advice。
最常使用的是每類Advice。它適用于一般的Advice,例如事務Advisors。這些不依賴于代理對象的狀態或添加新狀態。它們只是對方法和參數起作用。
每個實例Advice適用于引入,以支持mixin。在這種情況下,通知將狀態添加到代理對象。
你可以在同一個AOP代理中混合使用共享通知和每個實例通知。
Advice類型
Spring提供了幾種通知類型,并且可以擴展以支持任意通知類型。
Spring中最基本的通知類型是圍繞通知的攔截。
Spring與AOP Alliance接口兼容,支持使用方法攔截的環繞通知。實現MethodInterceptor和around advice的類還應該實現以下接口:
public interface MethodInterceptor extends org.aopalliance.intercept.Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
invoke()方法的MethodInvocation參數公開了被調用的方法、目標連接點、AOP代理和方法的參數。invoke()方法應該返回調用的結果:連接點的返回值。
面的例子展示了一個簡單的MethodInterceptor實現:
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
注意:MethodInvocation中對proceed()方法的調用。這將沿著攔截器鏈向下直至連接點。大多數攔截器調用此方法并返回其返回值。然而,MethodInterceptor和任何around通知一樣,可以返回不同的值或拋出異常,而不是調用proceed方法。然而,如果沒有充分的理由,你不會想要這樣做。
MethodInterceptor實現提供了與其他遵循AOP聯盟的AOP實現的互操作性。雖然使用最具體的通知類型有好處,但如果你可能想在另一個AOP框架中運行方面,請堅持使用MethodInterceptor。注意,切入點目前不能在框架之間互操作,而且AOP聯盟目前不定義切入點接口。
一個簡單的Advice類型是事前Adivce。它不需要MethodInvocation對象,因為它只在進入方法之前被調用。
before通知的主要優點是不需要調用proceed()方法,因此不可能在無意中無法繼續執行攔截器鏈。
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
注意:返回類型是void。Before通知可以在連接點運行之前插入自定義行為,但不能更改返回值。如果before通知拋出異常,它將停止攔截器鏈的進一步執行。異常在攔截器鏈中向上傳播。如果未檢查或在被調用方法的簽名上,它將直接傳遞給客戶端。否則,它將被AOP代理包裝在未檢異常中。
下面的例子展示了Spring中的before通知,它統計了所有的方法調用:
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount(){
return count;
}
}
如果連接點拋出異常,則在連接點返回后調用Throws通知。Spring提供了類型化異常通知。注意,這意味著org.springframework.aop.ThrowsAdvice接口不包含任何方法。它是一個標記接口,標識給定對象實現了一個或多個類型化throws通知方法。格式如下:
afterThrowing([Method, args, target], subclassOfThrowable)
Method,args,target3個參數是可選的。
public class BusinessThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(BusinessException ex) throws Throwable {
// ...
}
}
下一個示例聲明了4個參數,因此它可以訪問被調用的方法、方法參數和目標對象。如果拋出ServletException,將調用以下Advice:
public class ControllerAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, MethodArgumentNotValidException ex){
// ...
}
}
在一個異常通知類中定義多個不同異常的處理
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(BusinessException ex) throws Throwable {
// ...
}
public void afterThrowing(Method m, Object[] args, Object target, MethodArgumentNotValidException ex){
// ...
}
}
Spring中的后置通知必須實現org.springframework.aop.AfterReturningAdvice接口,如下:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable;
}
返回通知可以訪問返回值(它不能修改)、被調用的方法、方法的參數和目標。
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount(){
return count;
}
}
如果它拋出異常,它將被拋出攔截器鏈,而不是返回值。
Spring將引介通知視為一種特殊的攔截通知。
Introduction需要一個IntroductionAdvisor和一個IntroductionInterceptor實現以下接口:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
從AOP Alliance方法攔截器接口繼承的invoke()方法必須實現引入。也就是說,如果被調用的方法在引入的接口上,則引入攔截器負責處理方法調用—它不能調用proceed()。
引介通知不能與任何切入點一起使用,因為它只適用于類級別,而不是方法級別。你只能在 IntroductionAdvisor中使用介紹建議,它有以下方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}
沒有MethodMatcher,因此也沒有與引介通知相關聯的切入點。只有類過濾。
getInterfaces()方法返回這個Advisor引入的接口。
validateInterfaces()方法在內部使用,以查看引入的接口是否可以由配置的IntroductionInterceptor實現。下面直接給出示例,該示例的作用就是使某個類不具備某個接口能力時動態給予該接口的能力:
接口:
public interface CountDAO {
public void count() ;
}
這里的引介攔截器必須實現我們期望的一個接口:
public class CustomIntroductionInterceptor implements IntroductionInterceptor, CountDAO {
@Override
public void count(){
System.out.println("訂單統計...") ;
}
@Override
public boolean implementsInterface(Class<?> intf){
return CountDAO.class.isAssignableFrom(intf) ;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (implementsInterface(invocation.getMethod().getDeclaringClass())) {
System.out.println("我是Introduction增強..." + "Class: " + invocation.getMethod().getDeclaringClass() + ", method: " + invocation.getMethod().getName()) ;
// 實際調用的就是當前Advice實現的CountDAO#count方法。
return invocation.getMethod().invoke(this, invocation.getArguments()) ;
}
return invocation.proceed() ;
}
}
創建代理處理器:
@Component
public class OrderProxyCreater extends AbstractAutoProxyCreator {
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,
TargetSource customTargetSource) throws BeansException {
return new Object[] {new DefaultIntroductionAdvisor(new CustomIntroductionInterceptor(), CountDAO.class)} ;
}
// 判斷只要不是OrderDAO類型的都進行跳過(這里只代理是OrderDAO類型的Bean)
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName){
return !OrderDAO.class.isAssignableFrom(beanClass) ;
}
}
OrderDAO實現,該DAO并沒有實現CountDAO:
@Service
public class OrderDAOImpl implements OrderDAO {
@Override
public void save(){
System.out.println("保存訂單...") ;
}
@Override
public void query(){
System.out.println("查詢訂單...") ;
}
}
測試:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.pack.aop") ;
ctx.registerShutdownHook();
OrderDAO persondao = ctx.getBean(OrderDAO.class) ;
persondao.save() ;
Object obj = ctx.getBean("orderDAOImpl") ;
if (obj instanceof CountDAO) {
CountDAO cdao = (CountDAO) obj ;
cdao.count() ;
}
運行結果:
保存訂單...
我是Introduction增強...Class: interface com.pack.aop.CountDAO, method: count
從運行結果看到OrderDAO具備了CountDAO接口能力,而具體實現CountDAO是我們的引介攔截器上實現的。