Spring4入門之第三章AOP的XML方式
AOP的概述
- 引用百度的一段詳解:
-
不修改程序源代碼的情況下對程序進行增強。
比如可以進行權限校驗、日誌記錄、性能監控和事務控制等等。
-
AOP最早由AOP聯盟的組織提出的,並制定了一套規範,Spring將AOP思想引入到框架當中,必須遵守AOP聯盟的規範。
-
AOP面向切面編程。AOP是OOP的擴展和延伸,解決OOP開發中遇到的問題。
AOP的底層實現
JDK動態代理
-
JDK動態代理是代理模式的一種實現方式,其只能代理接口。
-
爲接口中的save方法在執行之前,進行權限校驗。
-
使用步驟:
1、 新建一個接口
2、 爲接口創建一個實現類
3、 創建代理類實現java.lang.reflect.InvocationHandler接口
4、 測試
-
首先新建一個接口UserDao.java
public interface UserDao { public void save(); public void update(); public void find(); public void delete(); }
-
然後爲接口UserDao.java新建一個實現類UserDaoImpl.java
public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("保存用戶。。。"); } @Override public void update() { System.out.println("修改用戶。。。"); } @Override public void find() { System.out.println("查詢用戶。。。"); } @Override public void delete() { System.out.println("刪除用戶。。。"); } }
-
創建一個代理類JdkProxy實現java.lang.reflect.InvocationHandler接口,重寫invoke方法
/** * 使用JDK動態代理對UserDao產生代理 * @author SYJ */ public class JdkProxy implements InvocationHandler { private UserDao userDao; public JdkProxy(UserDao userDao) { this.userDao = userDao; } /** * @Title: creatUserDaoProxy * @Description: TODO(產生UserDao的方法) * @param @param dao * @param @return * @return UserDao */ public UserDao creatUserDaoProxy() { UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this); return userDaoProxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("save".equals(method.getName())) { System.out.println("權限校驗========="); } return method.invoke(userDao, args); } }
Cglib動態代理
-
CGLIB(Code Generation Library)是一個開源項目!是一個強大的,高性能,高質量的Code生成類庫。
-
它可以在運行期擴展Java類與實現Java接口。Hibernate用它來實現PO(Persistent Object 持久化對象)字節碼的動態生成。
-
CGLIB是一個強大的高性能的代碼生成包。它廣泛的被許多AOP的框架使用,例如Spring AOP爲他們提供方法的interception(攔截)。CGLIB包的底層是通過使用一個小而快的字節碼處理框架ASM,來轉換字節碼並生成新的類。
-
除了CGLIB包,腳本語言例如Groovy和BeanShell,也是使用ASM來生成java的字節碼。當然不鼓勵直接使用ASM,因爲它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。
-
實現一個業務類,注意,這個業務類並沒有實現任何接口:
CustomerDao
public class CustomerDao { public void save() { System.out.println("客戶保存。。。"); } public void update() { System.out.println("客戶修改。。。"); } public void find() { System.out.println("客戶查找。。。"); } public void delete() { System.out.println("客戶刪除。。。"); } }
自定義CglibProxy類實現MethodInterceptor接口
/** * Cglib實現動態代理 * @author SYJ */ public class CglibProxy implements MethodInterceptor { private CustomerDao customerDao; public CglibProxy(CustomerDao customerDao) { this.customerDao = customerDao; } /** * @Title: creatCustomerDaoProxy * @Description: TODO(使用cglib產生代理的方法) * @param @return * @return CustomerDao */ public CustomerDao creatCustomerDaoProxy() { // 1.創建cglib的核心類對象 Enhancer enhancer = new Enhancer(); // 2.設置父類 enhancer.setSuperclass(customerDao.getClass()); // 3.設置回調 enhancer.setCallback(this); // 4.產生代理對象 CustomerDao customerDaoProxy = (CustomerDao) enhancer.create(); return customerDaoProxy; } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if ("save".equals(method.getName())) { System.out.println("Cglib的權限校驗========="); } return methodProxy.invokeSuper(proxy, args); } }
-
這個接口只有一個intercept()方法,這個方法有4個參數:
1)proxy表示增強的對象,即實現這個接口類的一個對象;
2)method表示要被攔截的方法;
3)args表示要被攔截方法的參數;
4)methodProxy表示要觸發父類的方法對象;
AOP的相關術語
-
相關術語
- Aspect(切面): Aspect 聲明類似於 Java 中的類聲明,在 Aspect 中會包含着一些 Pointcut 以及相應的 Advice。
- Joint point(連接點):表示在程序中明確定義的點,典型的包括方法調用,對類成員的訪問以及異常處理程序塊的執行等等,它自身還可以嵌套其它 joint point。
- Pointcut(切點):表示一組 joint point,這些 joint point 或是通過邏輯關係組合起來,或是通過通配、正則表達式等方式集中起來,它定義了相應的 Advice 將要發生的地方。
- Advice(增強、通知):Advice 定義了在 Pointcut 裏面定義的程序點具體要做的操作,它通過 before、after 和 around 來區別是在每個 joint point 之前、之後還是代替執行的代碼。
- Target(目標對象):織入 Advice 的目標對象.。
- Weaving(織入):將 Aspect 和其他對象連接起來, 並創建 Adviced object 的過程
- introduce(引入):爲對象引入附加的方法或屬性,從而達到修改對象結構的目的。有的AOP工具又將其稱爲mixin。
-
圖解AOP
-
AOP的Advice類型
-
前置增強(Before advice):(可以獲取切入點信息)
在某連接點之前執行的增強,但這個增強不能阻止連接點前的執行(除非它拋出一個異常)
-
後置返回增強(After returning advice):
在某連接點正常完成後執行的增強:例如,一個方法沒有拋出任何異常,正常返回。
-
環繞增強(Around Advice):
包圍一個連接點的增強,如方法調用。這是最強大的一種增強類型。 環繞增強可以在方法調用前後完成自定義的行爲。它也會選擇是否繼續執行連接點或直接返回它們自己的返回值或拋出異常來結束執行。
-
後置異常增強(After throwing advice):
在方法拋出異常退出時執行的增強。
-
後置最終增強(After (finally) advice):
當某連接點退出的時候執行的增強(不論是正常返回還是異常退出)。
-
-
AOP的基本運行流程(來源於網絡)
Spring的AOP的入門(AspectJ的Xml方式)
-
傳統的AOP開發(Aop聯盟)和基於AspectJ的AOP開發(AspcetJ和Spring的整合)
-
爲接口CustomerDao中的save方法在執行之前,進行權限的校驗
-
CustomerDao.java
public interface ProductDao { public void save(); public void update(); public void find(); public String delete(); }
-
ProductDaoImpl.java
public class ProductDaoImpl implements ProductDao { @Override /* * 增強save方法 * */ public void save() { System.out.println("保存商品"); } @Override public void update() { System.out.println("修改商品"); } @Override public void find() { System.out.println("查詢商品"); } @Override public String delete() { System.out.println("刪除商品"); return "delete method"; } }
-
編寫切面類:
public class MyAspect { /** * @Title: chekPri * @Description: TODO(前置通知) * @param joinPoint * @return void */ public void chekPri() { System.out.println("權限校驗========="); } }
-
編寫Spring的配置文件,將CustomerDao的實現類ProductDaoImpl和自己編寫的切面類都交給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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --> <!-- 配置目標對象,被增強的對象 --> <bean id="productDao" class="com.syj.Spring.aop.ProductDaoImpl" /> <!-- 將切面交給Spring管理 --> <bean id="myAspect" class="com.syj.Spring.aop.MyAspect"/> <!-- 通過AOP的配置完成對目標類產生代理 --> <aop:config> <!-- 定義一個切入點,通過表達式配置哪些類的哪些方法需要增強 --> <aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.save(..))" id="pointcut1"/> <!-- 配置切面,要給什麼類的什麼方法在什麼時候執行什麼方法 --> <aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> </aop:aspect> </aop:config> </beans>
-
運行結果,在執行save方法之前首先進行權限的校驗
-
-
AOP開發的通知類型
-
前置通知(獲取切入點信息)
在save方法執行前進行權限的校驗,就是上面的入門代碼。
定義一個切入點,通過表達式配置哪些類的哪些方法需要增強
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.save(..))" id="pointcut1"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> </aop:aspect>
切面類的增強方法:
/** * * @Title: chekPri * @Description: TODO(前置通知) * @param joinPoint * @return void */ public void chekPri(JoinPoint joinPoint) { System.out.println("權限校驗=========" + joinPoint);//joinPoint獲取切入點信息 }
-
後置通知(可以獲取返回值)
在delete執行之後執行日誌的打印
定義一個切入點,通過表達式配置哪些類的哪些方法需要增強
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.delete(..))" id="pointcut2"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> <!-- 後置打印日誌 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result" /> </aop:aspect>
切面類的增強方法
/** * @Title: writeLog * @Description: TODO(後置通知) * @param * @return void */ public void writeLog(Object result) { //會得到ProductDaoImpl中delete的返回值 System.out.println("日誌記錄=========" + result);//配置切面的returning屬性值一致 }
-
環繞通知()
測試update方法的性能
定義一個切入點,通過表達式配置哪些類的哪些方法需要增強
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.update(..))" id="pointcut3"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> <!-- 後置打印日誌 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result" /> <!-- 對數據的修改進行性能的監控 --> <aop:around method="around" pointcut-ref="pointcut3" /> </aop:aspect>
切面類的增強方法
/** * 性能的監控 * * @throws Throwable */ public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("環繞前通知==========="); Object obj = joinPoint.proceed(); System.out.println("環繞後通知==========="); return obj; }
-
異常拋出通知(可以獲取異常信息)
在find方法執行如果拋出異常時的解決辦法
定義一個切入點,通過表達式配置哪些類的哪些方法需要增強
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.find(..))" id="pointcut4"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> <!-- 後置打印日誌 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result" /> <!-- 對數據的修改進行性能的監控 --> <aop:around method="around" pointcut-ref="pointcut3" /> <!-- 異常拋出的通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex" /> </aop:aspect>
切面類的增強方法
/** * * @Title: afterThrowing * @Description: TODO(異常拋出通知) * @param * @return void */ public void afterThrowing(Throwable ex) { System.out.println("異常拋出通知===========" + ex);//對應aop:after-throwing中throwing屬性值 }
-
最終通知(異常中的finally)
定義一個切入點,通過表達式配置哪些類的哪些方法需要增強
切入點依然選擇find方法,在異常中測試最終通知
<aop:pointcut expression="execution(* com.syj.Spring.aop.ProductDaoImpl.find(..))" id="pointcut4"/>
配置切面
<aop:aspect ref="myAspect" > <!-- 前置通知 --> <aop:before method="chekPri" pointcut-ref="pointcut1" /> <!-- 後置打印日誌 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result" /> <!-- 對數據的修改進行性能的監控 --> <aop:around method="around" pointcut-ref="pointcut3" /> <!-- 異常拋出的通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex" /> <!-- 最終通知 --> <aop:after method="after" pointcut-ref="pointcut4" /> </aop:aspect>
切面類的增強方法
/** * * @Title: after * @Description: TODO(最終通知,最後一定會執行) * @param * @return void */ public void after() { System.out.println("最終一定會執行=========="); }
-
Spring的Pointcut表達式之execution
-
基於execution的函數完成的
-
語法
- [訪問修飾符] 方法返回值 包名.類型.方法名(參數)
- public void com.syj.spring.CustomerDao.save(…)
- * *.*. *. *Dao.save(…) 包的的所有Dao結尾的save方法執行
- * com.syj.spring.CustomerDao+.save(…) +代表當前的類和其子類
- * com.syj.spring…*.*(…) 這個包以及子包下面的所有類的所有方法