AOP: Aspect-Oriented Programming 面向切面程式設計。
首先明確一個點:AOP是一個概念。那麼對於一個概念,其實現方式多種多樣,分為靜態AOP、動態AOP,而對於動態AOP的實現又分為動態代理和動態位元組碼增強實現。
這裡分享的AOP主要是基於Spring的AOP實現。
AOP的一些基礎概念
-
連線點(Joinpoint):程式執行的某個特定位置,比如方法呼叫前、方法呼叫後、方法丟擲異常後等,這些點都可以是連線點。
-
切點(PointCut):一個程式有若干個連線點,並不是所有的連線點都是需要關注的,需要我們關注的那部分連線點就是切點。
-
通知(增強)(Advice):就是在切點處要插入的邏輯;分為前置、後置、異常、返回、環繞通知;
-
切面(Advice):切點和通知組合在一起形成切面,對應的切面橫切整個應用程式。切面實現了橫切關注點的模組化。
-
目標物件(Target):需要被增強的業務物件。
-
織入(Weaving):將通知增強新增到目標物件的具體連線點的過程。具體來說,就是生成代理物件並將切面融入到業務流程的過程。
-
代理類(Proxy):一個類在被織入器織入增強邏輯後產生的一個類就是代理類。
上面這些點在Spring中的體現就是一個一個的註解。
Spring AOP的使用方式
比如我們現在有一個除法計算類,這個方法計算可能會拋錯,在拋錯的時候我們需要記錄一下日誌,那麼使用方式如下:
(說明:很多地方在介紹AOP的時候,都喜歡以日誌來當做案例,但是日誌記錄並不是一個好的AOP使用場景,因為系統中各個日誌記錄點和記錄格式不具有通用性。)
-
首先定義實現方法:
public class MathCalculator { public int div(int i, int j) { return i / j; } }
-
然後定義切面:
@Aspect public class LogAspects { @Pointcut("execution(public int MathCalculator.*(..))") public void poinCut() { } @Around("poinCut()") public Object logArround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("環繞開始!"); try { return joinPoint.proceed(); } catch (Exception e) { System.out.println("環繞異常!"); throw e; } finally { System.out.println("環繞結束!"); } } }
我們在切面中定義了一個環繞通知,這樣就對MathCalculator的div方法呼叫進行了環繞處理。
Spring AOP的實現方式
上面提到動態AOP有兩種實現方式:動態代理和動態位元組碼增強。
在Spring中主要是通過動態代理實現的:基於JDK的動態代理和基於CGLIB的動態代理。
要了解Spring AOP的實現原理,只要把上面兩個技術瞭解後就能明白相應的原理了。
JDK動態代理
-
1、JDK動態代理是基於介面的,所以我們首先來建立一個介面
/** * 抽象主題介面 **/ public interface Subject { void doSomething(); }
-
2、建立對應介面的實現類
/** * 真實主題類 **/ public class RealSubject implements Subject { @Override public void doSomething() { System.out.println("doSomething...."); } }
-
3、建立代理類,實現 java.lang.reflect.InvocationHandler 介面
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * jdkd動態代理類 **/ public class JDKDynamicProxy implements InvocationHandler { private Object target; public JDKDynamicProxy(Object target) { this.target = target; } /** * 獲取被代理介面例項物件 */ public <T> T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Do something before"); Object result = method.invoke(target, args); System.out.println("Do something after"); return result; } }
-
4、建立測試類
import com.lnjecit.proxy.dynamic.jdk.JDKDynamicProxy; /** * 測試類 **/ public class Ceshi { public static void main(String[] args) { Subject subject = new JDKDynamicProxy(new RealSubject()).getProxy(); subject.doSomething(); } }
像這樣執行Ceshi類的main方法就能發現對應的doSomething()方法被我們的邏輯進行了攔截。
-
5、動態代理原理
我們開啟生成的代理類,可以看到內容如下:
import com.lnjecit.proxy.Subject; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements Subject { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final void doSomething() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } // 為了精簡程式碼,下面省略了重寫的 equals、hashCode、toString、toString 方法 static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.lnjecit.proxy.Subject").getMethod("doSomething"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
可以看到,動態生成的代理類有如下特性:
1、繼承了Proxy類,實現了代理的介面,由於java不能多繼承,這裡已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支援對實現類的代理,只支援介面的代理。
2、提供了一個使用InvocationHandler作為引數的構造方法。
3、生成靜態程式碼塊來初始化介面中方法的Method物件,以及Object類的equals、hashCode、toString方法。
4、重寫了Object類的equals、hashCode、toString,它們都只是簡單的呼叫了InvocationHandler的invoke方法,即可以對其進行特殊的操作,也就是說JDK的動態代理還可以代理上述三個方法。
5、代理類實現代理介面的sayHello方法中,只是簡單的呼叫了InvocationHandler的invoke方法,我們可以在invoke方法中進行一些特殊操作,甚至不呼叫實現的方法,直接返回。
上面的InvocationHandler就是我們實現橫切邏輯的地方,它是橫切邏輯的載體。
CGLIB動態代理
CGLIB使用如下:
-
1、定義代理目標類
public class UserService { public String getUserName(Long userId) { System.out.println("獲取使用者名稱.."); return "user" + userId; } }
-
2、實現MethodInterceptor,定義事務攔截器
public class TransactionInterceptor implements MethodInterceptor { Object target; public TransactionInterceptor(Object target) { this.target = target; } /** * proxy:代理物件,CGLib動態生成的代理類例項 * method:目標物件的方法,上文中實體類所呼叫的被代理的方法引用 * args:目標物件方法的引數列表,引數值列表 * methodProxy:代理物件的方法,生成的代理類對方法的代理引用 */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("開啟事務..." + proxy.getClass().getSimpleName()); Object objValue = null; try { // 反射呼叫目標類方法 objValue = method.invoke(target, args); System.out.println("返回值為:" + objValue); } catch (Exception e) { System.out.println("呼叫異常!" + e.getMessage()); } finally { System.out.println("呼叫結束,關閉事務..."); } return objValue; } /** * 獲取代理例項 */ public Object getTargetProxy() { // Enhancer類是cglib中的一個位元組碼增強器,它可以方便的為你所要處理的類進行擴充套件 Enhancer eh = new Enhancer(); // 1.將目標物件所在的類作為Enhancer類的父類 eh.setSuperclass(target.getClass()); // 2.通過實現MethodInterceptor實現方法回撥 eh.setCallback(this); // 3. 建立代理例項 return eh.create(); } }
-
3、使用
public static void main(String[] args) { // 1. 建立目標例項 UserService userService = new UserService(); // 2. 建立事務攔截器 TransactionInterceptor transactionInterceptor = new TransactionInterceptor(userService); // 3. 建立代理例項 UserService userServiceProxy = (UserService) transactionInterceptor.getTargetProxy(); // 4. 使用代理例項呼叫目標方法 userServiceProxy.getUserName(6L); }
說明:CGLIB是基於繼承來實現的。它首先建立一個類,繼承代理目標類,然後重寫其中的方法,然後在重寫的方法中將呼叫委託給目標物件,然後在委託呼叫前後實現橫切邏輯。
通過上面兩個例子可以看出,JDK的動態代理不是通過繼承實現的,那麼就是通過實現介面實現的。而CGLIB則是通過動態生成代理類的子類來實現的。