coding++:Spring 中的 AOP 原理

爲何使用 AOP 以下場景:正則表達式

如今有一個情景:spring

咱們要把大象放進冰箱,步驟爲:打開冰箱->放入大象->關閉冰箱編程

若是再把大象拿出來,步驟爲:打開冰箱->拿出大象->關閉冰箱數組

代碼以下:框架

 public void put() {
        System.out.println("打開冰箱...");
        System.out.println("放入大象...");
        System.out.println("關閉冰箱...");
    }
 
    public void get() {
        System.out.println("打開冰箱...");
        System.out.println("拿出大象...");
        System.out.println("關閉冰箱...");
    }

 

咱們須要在每個拿進拿出操做先後都要進行打開冰箱和關閉冰箱的操做,形成了代碼重複。ide

而若是要拿進拿出其餘動物,那麼每個動物的操做都須要加入打開冰箱關閉冰箱的操做,十分繁瑣混亂。函數

解決方法就是AOP,將這些打開冰箱和關閉冰箱的操做單獨抽取出來,作成一個切面,以後調用任何方法,都插入到方法先後便可。測試

先來看一些基本概念再來解決這個問題。this

基本概念:編碼

AOP   即Aspect Oriented Program,面向切面編程

使用AOP技術,能夠將一些系統性相關的編程工做,獨立提取出來,獨立實現,而後經過切面切入進系統。

從而避免了在業務邏輯的代碼中混入不少的系統相關的邏輯——好比權限管理,事物管理,日誌記錄等等。

這些系統性的編程工做均可以獨立編碼實現,而後經過AOP技術切入進系統便可。從而達到了 將不一樣的關注點分離出來的效果。

切面(Aspect):其實就是共有功能的實現。

          如日誌切面、權限切面、事務切面等。

           在實際應用中一般是一個存放共有功能實現的普通Java類,之因此能被AOP容器識別成切面,是在配置中指定的。

通知/加強(Advice):是切面的具體實現。以目標方法爲參照點,根據放置的地方不一樣,可分爲前置通知(Before)、後置通知(AfterReturning)、異常通知(AfterThrowing)、最終通知(After)與環繞通知(Around)5種。

            在實際應用中一般是切面類中的一個方法,具體屬於哪類通知,一樣是在配置中指定的。

鏈接點(Joinpoint):就是程序在運行過程當中可以插入切面的地點。

            例如,方法調用、異常拋出或字段修改等,但Spring只支持方法級的鏈接點。

切入點(Pointcut):用於定義通知應該切入到哪些鏈接點上。

          不一樣的通知一般須要切入到不一樣的鏈接點上,這種精準的匹配是由切入點的正則表達式來定義的。

目標對象(Target):就是那些即將切入切面的對象,也就是那些被通知的對象。

                    這些對象中已經只剩下乾乾淨淨的核心業務邏輯代碼了,全部的共有功能代碼等待AOP容器的切入。

代理對象(Proxy):將通知應用到目標對象以後被動態建立的對象。

          能夠簡單地理解爲,代理對象的功能等於目標對象的核心業務邏輯功能加上共有功能。

           代理對象對於使用者而言是透明的,是程序運行過程當中的產物。

織入(Weaving):將切面應用到目標對象從而建立一個新的代理對象的過程。

         這個過程能夠發生在編譯期、類裝載期及運行期,固然不一樣的發生點有着不一樣的前提條件。

         譬如發生在編譯期的話,就要求有一個支持這種AOP實現的特殊編譯器;發生在類裝載期,就要求有一個支持AOP實現的特殊類裝載器;只有發生在運行期,則可直接經過Java語言的反射機制與動態代理機制來動態實現。

AOP 原理:

AOP 代理可分爲靜態代理和動態代理兩大類,

靜態代理:使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,所以也稱爲編譯時加強;

動態代理:在運行時藉助於 JDK 動態代理、CGLIB(code generate libary)字節碼生成技術 等在內存中「臨時」生成 AOP 動態代理類,所以也被稱爲運行時加強

Spring AOP採用的是動態代理,在運行期間對業務方法進行加強,因此不會生成新類。

對於動態代理技術,Spring AOP提供了對JDK動態代理的支持以及CGLib的支持。

前者是基於反射技術的實現,後者是基於繼承的機制實現。

若是目標對象有實現接口,使用jdk代理。

若是目標對象沒有實現接口,則使用Cglib代理。

 

JDK:動態代理:

JDK動態代理須要得到被目標類的接口信息(應用Java的反射),生成一個實現了代理接口的動態代理類(字節碼),再經過反射機制得到動態代理類的構造函數,利用構造函數生成動態代理類的實例對象,在調用具體方法前調用

invokeHandler方法來處理。

主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。

JDK動態代理要求被代理的類實現一個接口,只有接口中的方法纔可以被代理 。

其方法是將被代理對象注入到一箇中間對象,而中間對象實現InvocationHandler接口,在實現該接口時,能夠在被代理對象調用它的方法時,在調用的先後插入一些代碼。

而 Proxy.newProxyInstance() 可以利用中間對象來生產代理對象。

插入的代碼就是切面代碼。因此使用JDK動態代理能夠實現AOP。

如今演示一下如何使用JDK動態代理實現開頭的情景

JDK動態代理須要被代理類實現一個接口,先寫一個接口。

public interface AnimalOperation {
    public void put();
    public void get();
}

再寫一個類(要被代理的類),實現這個接口

public class ElephantOperation implements AnimalOperation{
 
    public void put() {
        System.out.println("放入大象...");
    }
 
    public void get() {
        System.out.println("拿出大象...");
    }
}

而後寫一個類來實現InvocationHandler接口,在該類中對被代理類的方法作加強,並編寫生成代理對象的方法

public class FridgeJDKProxy implements InvocationHandler{
    //被代理的對象,以後用反射調用被代理方法的時候須要被代理對象的引用
    private Object target;
 
    //InvocationHandler接口的方法,
    // proxy是代理對象,method是被代理的方法,args是被代理方法的參數,返回值是原方法的返回
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        openDoor();//調用被代理方法作一些操做
        Object result = method.invoke(target, args);//執行被代理對象的方法,若是方法有返回值則賦值給result
        closeDoor();//調用被代理方法後作一些操做
        return result;
    }
    private void openDoor(){
        System.out.println("打開冰箱...");
    }
    private void closeDoor(){
        System.out.println("關閉冰箱...");
    }
    public Object getProxy(Object target){
        this.target=target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}

其中Proxy.newProxyInstance()方法須要的參數分別爲,類加載器ClassLoader loader,接口數組Class<?>[] interfaces,與 InvocationHandler 

測試代碼爲:

  public static void main(String args[]) {
      AnimalOperation elephantOperation =(AnimalOperation) new FridgeJDKProxy().getProxy(new ElephantOperation());
      elephantOperation.put();
      elephantOperation.get();
  }

打印結果:

 

CGLIB 動態代理:

字節碼生成技術實現AOP,其實就是繼承被代理對象,而後Override須要被代理的方法,在覆蓋該方法時,天然是能夠插入咱們本身的代碼的。

CGLib動態代理須要依賴asm包,把被代理對象類的class文件加載進來,修改其字節碼生成子類。

由於須要Override被代理對象的方法,因此天然CGLIB技術實現AOP時,就 必需要求須要被代理的方法不能是final方法,由於final方法不能被子類覆蓋 。

如今演示一下如何使用CGLIB動態代理實現開頭的情景

CGLIB動態代理不要求被代理類實現接口,先寫一個被代理類。

public class MonkeyOperation {
    public void put() {
        System.out.println("放入猴子...");
    }
 
    public void get() {
        System.out.println("拿出猴子...");
    }
}

在寫一個類實現MethodInterceptor接口,並在接口方法intercept()裏對被代理對象的方法作加強,並編寫生成代理對象的方法

public class FridgeCGLibProxy implements MethodInterceptor {
 
    public String name="hahaha";
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        openDoor();//調用被代理方法作一些操做
        Object result = methodProxy.invokeSuper(proxy,args);//執行被代理對象的方法,若是方法有返回值則賦值給result
        closeDoor();//調用被代理方法後作一些操做
        return result;
    }
    private void openDoor(){
        System.out.println("打開冰箱...");
    }
    private void closeDoor(){
        System.out.println("關閉冰箱...");
    }
    public Object getProxy(Class cls){//參數爲被代理的類對象
        Enhancer enhancer = new Enhancer();//建立加強器,用來建立動態代理類
        enhancer.setSuperclass(cls);//設置父類,即被代理的類對象
        enhancer.setCallback(this);//設置回調,指定爲當前對象
        return enhancer.create();//返回生成的代理類
    }
}

測試代碼:

  public static void main(String args[]) {
      MonkeyOperation monkeyOperation =(MonkeyOperation)new FridgeCGLibProxy().getProxy(MonkeyOperation.class);
      monkeyOperation.put();
      monkeyOperation.get();
  }

打印結果:

spring實現AOP,若是被代理對象實現了接口,那麼就使用JDK的動態代理技術,反之則使用CGLIB來實現AOP,因此 Spring默認是使用JDK的動態代理技術實現AOP的 。

相關文章
相關標籤/搜索