一、什麼是AOP(Aspect Oriented Programming)
在軟件業,AOP(Aspect Oriented Programming的縮寫)爲:面向切面編程
AOP是一種通過預編譯方式和運行期動態代理實現程序功能的統一維護的技術。
AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
------------百度百科
二、AOP相關術語
將AOP想象成一把能切開程序的刀,AOP將一個程序切開,在切開的位置又插入了一些功能。
術語 | 作用 | 理解 |
---|---|---|
連接點(Joinpoint) | 在程序運行過程中被攔截到的點,在spring中只支持方法類型的連接點,所以這裏的連接點指的是被攔截到的方法 | 這個程序中可以下刀的位置,一般從方法處切開程序,可以切的地方 |
切入點(Pointcut) | 指我們要對那些連接點(Joinpoint)進行攔截的定義 | 指定我們要下刀的位置,想要切的地方 |
切面(Aspect) | 是指封裝橫向切到系統功能的類(例如事務處理),是切入點和通知的結合 | 切入點和通知的結合 |
通知(Advice) | 通知就是指攔截到的連接點之後所要做的事情,因此通知是切面的具體實現;通知分爲前置通知、後置通知、異常通知、最終通知、環繞通知 | 往程序切開的地方插入功能,這個功能叫做通知 |
引介(Introduction) | 也被稱爲引入,允許在現有的實現類中添加自定義的方法和屬性; | 動態地往類中插入一個新的方法 |
目標對象(Target Object) | 是指被通知的對象,即代理的目標對象 | 被切的對象 |
織入(Weaving) | 是將切面代碼插入到目標對象上,從而生成代理對象的過程 | 在切開的地方插入功能即爲織入 |
代理(Proxy) | 是通知應用到目標對象之後被動態創建的對象 | 一個對象被織入一些功能以後生成了一個新的對象,這個對象就是代理 |
三、AOP底層實現原理
AOP的底層是使用動態代理的方法實現的,在Java中有多種動態代理技術,如JDK、CGLIB、Javassist、ASM,其中最常用的動態代理技術是JDK和CGLIB。
1、JDK的動態代理
JDK動態代理是java.lang.reflect.*包提供的方法,必須要藉助一個接口才能產生代理對象(可以對實現接口的類進行代理),對於使用業務接口的類,Spring默認使用JDK動態代理實現AOP。
JDK的動態代理主要是使用java.lang.reflect.Proxy生成代理。但是這樣只能代理實現了接口的類。
- StuDao接口
public interface StuDao { public void add(); public void find(); public void update(); public void delete(); }
- StuDaoImpl實現類
public class StuDaoImpl implements StuDao { @Override public void add() { System.out.println("添加學生"); } @Override public void find() { System.out.println("查詢學生"); } @Override public void update() { System.out.println("修改學生"); } @Override public void delete() { System.out.println("刪除學生"); } }
- 創建aspect包,並創建切面類MyAspect,該類中可以定義多個通知,即增強處理的方法,示例代碼如下:
public class MyAspect { public void check(){ System.out.println("模擬權限控制"); } public void except(){ System.out.println("模擬異常處理"); } public void log(){ System.out.println("模擬日誌記錄"); } public void monitor(){ System.out.println("模擬性能檢測"); } }
- 創建proxy包,並創建代理類MyJdkProxy,在JDK動態代理中代理類必須實現java.lang.reflect.InvocationHandler接口,並編寫代理方法,在代理方法中需要通過Proxy實現動態代理。示例代碼如下:
package com.aop.proxy; import com.aop.aspect.MyAspect; import com.aop.dao.StuDao; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyJdkProxy implements InvocationHandler { //聲明目標類接口對象(真實對象) private StuDao stuDao; public MyJdkProxy(StuDao stuDao){ this.stuDao = stuDao; } //創建代理的方法,建立代理對象和真實對象的代理關係,返回代理對象 public Object createProxy(){ //1.類加載器 ClassLoader cld = MyJdkProxy.class.getClassLoader(); //2.被代理對象實現的所有接口 Class[] clazz = stuDao.getClass().getInterfaces(); return Proxy.newProxyInstance(cld,clazz,this); } /** * 代理的邏輯方法,所有動態代理類的方法調用都交給該方法處理 * @param proxy 被代理對象 * @param method 要執行的方法 * @param args 執行方法時需要的參數 * @return 返回代理結果 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //創建一個切面 MyAspect myAspect = new MyAspect(); //前增強 myAspect.check(); myAspect.except(); //在目標類上調用方法並傳入參數,相當於調用stuDao中的方法 Object obj = method.invoke(stuDao,args); //後增強 myAspect.log(); myAspect.monitor(); return obj; } }
- 創建測試類
@Test public void testStu(){ //創建目標對象 StuDao stuDao = new StuDaoImpl(); //創建代理對象 MyJdkProxy myJdkProxy = new MyJdkProxy(stuDao); //從代理對象中獲取增強後的目標對象 //該對象是一個被代理的對象,它會進入代理的邏輯方法invoke中 StuDao stuDaoProxy = (StuDao) myJdkProxy.createProxy(); //執行方法 stuDaoProxy.add(); System.out.println("=================="); stuDaoProxy.update(); System.out.println("=================="); stuDaoProxy.delete(); }
2、CGLIB的動態代理
JDK動態代理必須提供接口才能使用,對於沒有提供接口的類,只能採用CGLIB動態代理。CGLIB採用非常底層的字節碼技術,對指定的目標類生產一個子類,並對子類進行增強。在Spring Core 包中已經集成了CGLIB所需要的jar包,無需另外引入jar包。
Enhancer是CGLIB中使用頻率很高的一個類,它是一個字節碼增強器,可以用來爲無接口的類創建代理。CGLIB的動態代理中,主要就是使用Enhancer來繼承一個類,從而實現代理。
- 創建目標類TestDao
public class TestDao { public void save(){ System.out.println("保存方法"); } public void modify(){ System.out.println("修改方法"); } public void delete(){ System.out.println("刪除方法"); } }
- 創建切面類MyAspect,並在該類中定義多個通知
public class MyAspect { public void check(){ System.out.println("模擬權限控制"); } public void except(){ System.out.println("模擬異常處理"); } public void log(){ System.out.println("模擬日誌記錄"); } public void monitor(){ System.out.println("模擬性能檢測"); } }
- 創建代理類MyCglibProxy,並實現MethodInterceptor接口
package com.aop.proxy; import com.aop.aspect.MyAspect; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class MyCglibProxy implements MethodInterceptor { /** * 創建代理的方法,生成CGLIB代理對象 * @param target 目標對象,需要增強的對象 * @return 返回目標對象的CGLIB代理對象 */ public Object createProxy(Object target){ //創建一個動態類對象,即增強類對象 Enhancer enhancer = new Enhancer(); //設置其父類 enhancer.setSuperclass(target.getClass()); //確定代理邏輯對象爲當前對象 enhancer.setCallback(this); return enhancer.create(); } /** * 該方法會在程序執行目標方法時調用 * @param proxy 是CGLIB根據指定父類生成的代理對象 * @param method 是攔截方法 * @param args 攔截方法的參數數組 * @param methodProxy 方法的代理對象,用於執行父類的方法 * @return 返回代理結果 * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //創建一個切面 MyAspect myAspect = new MyAspect(); //前置增強 myAspect.check(); //目標方法執行,返回執行結果 Object obj = methodProxy.invokeSuper(proxy,args); //後置增強 myAspect.log(); return obj; } }
- 創建測試類
@Test public void test(){ //創建目標對象 TestDao testDao = new TestDao(); //創建代理對象 MyCglibProxy myCglibProxy = new MyCglibProxy(); //獲取增強後的目標對象 TestDao testDaoAdvice = (TestDao) myCglibProxy.createProxy(testDao); //執行方法 testDaoAdvice.save(); System.out.println("=================="); testDaoAdvice.modify(); System.out.println("=================="); testDaoAdvice.delete(); }
3、動態代理注意事項
- 程序中應優先對接口創建代理,便於程序解耦維護;
- 使用final關鍵字修飾的方法不能被代理,因爲無法覆蓋
- JDK動態代理,是針對接口生成子類,接口中方法不能使用final修飾
- CGLIB是針對目標類生成子類,因此類或方法不能使用final修飾
- Spring只支持方法連接點,不提供屬性連接點