spring的五種通知及其簡單使用
AOP中關鍵性概念
連線點(Joinpoint):程式執行過程中明確的點,如方法的呼叫,或者異常的丟擲.
目標(Target):被通知(被代理)的物件
注1:完成具體的業務邏輯
通知(Advice):在某個特定的連線點上執行的動作,同時Advice也是程式程式碼的具體實現,例如一個實現日誌記錄的程式碼(通知有些書上也稱為處理)
注2:完成切面程式設計
代理(Proxy):將通知應用到目標物件後建立的物件(代理=目標+通知),
例子:外科醫生+護士
注3:只有代理物件才有AOP功能,而AOP的程式碼是寫在通知的方法裡面的
切入點(Pointcut):多個連線點的集合,定義了通知應該應用到那些連線點。
(也將Pointcut理解成一個條件 ,此條件決定了容器在什麼情況下將通知和目標組合成代理返回給外部程式)
介面卡(Advisor):介面卡=通知(Advice)+切入點(Pointcut)
如何實現AOP
目標物件只負責業務邏輯程式碼
通知物件負責AOP程式碼,這二個物件都沒有AOP的功能,只有代理物件才有
1. AOP
即面向切面程式設計
2. AOP帶來的好處
讓我們可以 “專心做事”
案例:
public void doSameBusiness (long lParam,String sParam){
// 記錄日誌
log.info("呼叫 doSameBusiness方法,引數是:"+lParam);
// 輸入合法性驗證
if (lParam<=0){
throws new IllegalArgumentException("xx應該大於0");
}
if (sParam==null || sParam.trim().equals("")){
throws new IllegalArgumentException("xx不能為空");
}
// 異常處理
try{ ...
}catch(...){
}catch(...){
}
// 事務控制
tx.commit();
}
3 工具類org.springframework.aop.framework.ProxyFactoryBean用來建立一個代理物件,在一般情況下它需要注入以下三個屬性:
proxyInterfaces:代理應該實現的介面列表(List)
interceptorNames:需要應用到目標物件上的通知Bean的名字。(List)
target:目標物件 (Object)
4. 前置通知(org.springframework.aop.MethodBeforeAdvice):在連線點之前執行的通知()
案例:在購書系統當中使用AOP方式實現日誌系統
5. 後置通知(org.springframework.aop.AfterReturningAdvice):在連線點正常完成後執行的通知
案例:線上購書系統中,要求不修改BookBizImpl程式碼的情況下增加如下功能:對買書的使用者進行返利:每買本書返利3元。(後置通知)
即:每呼叫一次buy方法列印:“[銷售返利][時間]返利3元。”
6. 環繞通知(org.aopalliance.intercept.MethodInterceptor):包圍一個連線點的通知,最大特點是可以修改返回值,由於它在方法前後都加入了自己的邏輯程式碼,因此功能異常強大。
它通過MethodInvocation.proceed()來呼叫目標方法(甚至可以不呼叫,這樣目標方法就不會執行)
案例:修改日誌系統不光要輸出引數,還要輸出返回值(環繞通知)
# 這個接口裡面沒有定義方法,我們要求我們的類必須實現afterThrows這個方法
# public void afterThrowing( [Method method,] [Object args,] [Object target,] Throwable throwable );
# 前面三個引數都是可選的,只有第三個引數是必須的,同時我們還可以在同一個類中定義這個方法的多個版本,如:
# public void afterThrowing( MyException1 ex ) {}
# public void afterThrowing( MyException2 ex ) {}
# 具體那個方法被呼叫則根據具體的Exception來判斷,由AOP容器自動識別 執行
7. 異常通知(org.springframework.aop.ThrowsAdvice):這個通知會在方法丟擲異常退出時執行
案例: 書本價格為負數時丟擲一個異常,通過異常通知取消此訂單
8. 介面卡(org.springframework.aop.support.RegexpMethodPointcutAdvisor) 介面卡=通知(Advice)+切入點(Pointcut)
案例:通過介面卡解決發書評時也返利的問題
.*buy
9. 請使用介面接收返回的代理物件
A
B C
A a1 = new B();
A a2 = new C();
B b = (B) a2;
核心點
通知
前置通知
實現org.springframework.aop.MethodBeforeAdvice介面
買書、評論前加系統日誌
後置通知
實現org.springframework.aop.AfterReturningAdvice介面
買書返利(存在bug)
環繞通知
org.aopalliance.intercept.MethodInterceptor
類似過濾器,會包括切入點,目標類前後都會執行程式碼。
異常通知
org.springframework.aop.ThrowsAdvice
出現異常執行系統提示,然後進行處理。價格異常為例
過濾通知(介面卡)
org.springframework.aop.support.RegexpMethodPointcutAdvisor
處理買書返利的bug
在xml配置檔案中的部分程式碼
<!-- ************************************* aop ***************************************** -->
<!-- 目標物件 -->
<bean class="com.zking.aop.biz.impl.BookBizImpl" id="bookBiz">
</bean>
<!-- 前置通知 -->
<bean class="com.zking.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean>
<!-- 後置通知 -->
<bean class="com.zking.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
<!-- 環繞通知 -->
<bean class="com.zking.aop.advice.MyMethodInterceptor" id="myMethodInterceptor"></bean>
<!-- 異常通知 -->
<bean class="com.zking.aop.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean>
<!-- 過濾通知 -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="myAfterReturningAdvice2">
<property name="advice" ref="myAfterReturningAdvice"></property>
<!-- .*buy : [.*]指的是任意字元0~n個,以bug結尾的方法 -->
<!-- <property name="pattern" value=".*buy"></property> -->
<property name="patterns">
<list>
<value>.*buy</value>
</list>
</property>
</bean>
<!-- 生成代理(目標物件+通知) -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxyFactoryBean">
<!-- 目標物件 -->
<property name="target" ref="bookBiz"></property>
<!-- 代理工廠生產的代理需要實現的介面 -->
<property name="proxyInterfaces">
<list>
<value>com.zking.aop.biz.IBookBiz</value>
</list>
</property>
<!-- 日誌通知 -->
<property name="interceptorNames">
<list>
<value>myMethodBeforeAdvice</value>
<!-- <value>myAfterReturningAdvice</value> -->
<value>myAfterReturningAdvice2</value>
<!-- <value>myMethodInterceptor</value> -->
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
//前置通知
package com.zking.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 呼叫專案中的某一介面實現類的方式時,將類名,方法名,攜帶的引數,作為日誌儲存到資料庫。
* @author Machenike
*
*/
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
//獲取類名
String targetName = target.getClass().getName();
//獲取方法名
String methodName = method.getName();
//獲取所有引數
String params = Arrays.toString(args);
String msg = "【系統日誌】:->"+targetName+"."+methodName+" , 攜帶的參有:"+params;
System.out.println(msg);
}
}
//後置通知
package com.zking.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.AfterReturningAdvice;
public class MyAfterReturningAdvice implements AfterReturningAdvice{
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
//獲取類名
String targetName = target.getClass().getName();
//獲取方法名
String methodName = method.getName();
//獲取所有引數
String params = Arrays.toString(args);
System.out.println("【買書返利】->呼叫"+targetName+"."+method+",攜帶的引數:"+params+";目標物件所呼叫的方法的返回值"+returnValue);
}
}
package com.zking.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 環繞通知
* @author Machenike
*
*/
public class MyMethodInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Object target = invocation.getThis();
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
//獲取類名
String targetName = target.getClass().getName();
//獲取方法名
String methodName = method.getName();
//獲取所有引數
String params = Arrays.toString(args);
//前置通知
System.out.println("【環繞通知】:->"+targetName+"."+methodName+" , 攜帶的參有:"+params);
//放行後獲取放回值
Object returnValue = invocation.proceed();
//後置通知
System.out.println("【環繞通知】->呼叫"+targetName+"."+method+",攜帶的引數:"+params+";目標物件所呼叫的方法的返回值"+returnValue);
//必須要返回對應的值否則會報錯
return returnValue;
}
}
package com.zking.aop.advice;
import org.springframework.aop.ThrowsAdvice;
import com.zking.aop.ex.PriceException;
/**
* 異常通知
* @author Machenike
*
*/
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(PriceException pe) {
System.out.println("價格輸入有誤,購買失敗,請重新輸入");
};
}
//在類中的測試
package com.zking.aop.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.zking.aop.biz.IBookBiz;
public class AopTest {
public static void main(String[] args) {
//獲取spring上下文
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
//根據spring上下文獲取代理物件 生成的是代理物件不是實現類
//class com.sun.proxy.$Proxy3(代理物件的類) = com.zking.aop.biz.impl.BookBizImpl + com.zking.aop.advice.MyMethodBeforeAdvice
IBookBiz iBookBiz = (IBookBiz) applicationContext.getBean("proxyFactoryBean");
iBookBiz.buy("gay", "聖墟", 66d);
iBookBiz.comment("gay", "真好看");
}
}