1. 程式人生 > >動態代理與Spring AOP簡單介紹

動態代理與Spring AOP簡單介紹

代理模式

定義:給某個物件提供一個代理物件,並由代理物件控制對於原物件的訪問,即客戶不直接操控原物件,而是通過代理物件間接地操控原物件。

代ç模å¼UMLå¾
代理模式UML圖

代理的實現方式

  • 靜態代理:代理類是在編譯時就實現好的,Java 編譯完成後代理類是一個實際的 class 檔案。

  • 動態代理:代理類是在執行時生成的,Java 編譯完之後並沒有實際的 class 檔案,而是在執行時動態生成的類位元組碼,並載入到JVM中。

JDK動態代理 (基於介面代理)

Java實現動態代理的大致步驟如下:

  1. 定義一個委託類和公共介面。

  2. 自己定義一個類(呼叫處理器類,即實現 InvocationHandler 介面),這個類的目的是指定執行時將生成的代理類需要完成的具體任務(包括Preprocess和Postprocess),即代理類呼叫任何方法都會經過這個呼叫處理器類。

  3. 生成代理物件,需要指定委託物件實現的一系列介面呼叫處理器類的例項。

Java 實現動態代理主要涉及以下幾個類:

  • java.lang.reflect.Proxy: 這是生成代理類的主類,通過 Proxy 類生成的代理類都繼承了 Proxy 類,即 DynamicProxyClass extends Proxy

  • java.lang.reflect.InvocationHandler

    : 這裡稱他為"呼叫處理器",他是一個介面,我們動態生成的代理類需要完成的具體內容需要自己定義一個類,而這個類必須實現 InvocationHandler 介面。

 Java 實現動態代理的缺點:因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),只能針對介面建立代理類,不能針對類建立代理類。 

案例分析 

public class DynamicProxyDemo {
    public static void main(String[] args) {
        Subject realSubject = new RealSubject();    //1.建立委託物件
        ProxyHandler handler = new ProxyHandler(realSubject);   //2.建立呼叫處理器物件
        Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(),
                                                        RealSubject.class.getInterfaces(), handler);    //3.動態生成代理物件
        proxySubject.request(); //4.通過代理物件呼叫方法
    }
}

/**
 * 介面
 */
interface Subject{
    void request();
}

/**
 * 委託類
 */
class RealSubject implements Subject{
    public void request(){
        System.out.println("====RealSubject Request====");
    }
}
/**
 * 代理類的呼叫處理器
 */
class ProxyHandler implements InvocationHandler{
    private Subject subject;
    public ProxyHandler(Subject subject){
        this.subject = subject;
    }

    /**
    *這個函式是在代理物件呼叫任何一個方法時都會呼叫的,方法不同會導致第二個引數method不同,第一個 
    *引數是代理物件(表示哪個代理物件呼叫了method方法),第二個引數是 Method 物件(表示哪個方法 
    *被呼叫了),第三個引數是指定呼叫方法的引數。
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("====before====");//定義預處理的工作,當然你也可以根據 method 的不同進行不同的預處理工作
        Object result = method.invoke(subject, args);
        System.out.println("====after====");
        return result;
    }
}

Cglib代理(基於繼承代理)

  • JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.

  • Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)

  • Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.

Cglib子類代理實現方法:

1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可.

2.引入功能包後,就可以在記憶體中動態構建子類

3.代理的類不能為final,否則報錯

4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法 ,如果方法為static,private則無法進行代理。

案例分析

public class UserDao {

    public void save() {
        System.out.println("----已經儲存資料!----");
    }

}
/**
 * Cglib子類代理工廠
 * 對UserDao在記憶體中動態構建一個子類物件
 */
public class ProxyFactory implements MethodInterceptor{
    //維護目標物件
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //給目標物件建立一個代理物件
    public Object getProxyInstance(){
        //1.工具類
        Enhancer en = new Enhancer();
        //2.設定父類
        en.setSuperclass(target.getClass());
        //3.設定回撥函式
        en.setCallback(this);
        //4.建立子類(代理物件)
        return en.create();

    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("開始事務...");

        //執行目標物件的方法
        Object returnValue = method.invoke(target, args);

        System.out.println("提交事務...");

        return returnValue;
    }


    public static void main(String[] args){
        UserDao userDao = new UserDao();
        UserDao proxy=(UserDao)new ProxyFactory(userDao).getProxyInstance();
        proxy.save();
    }

}

Spring AOP

代理機制:

  1、若目標物件實現了若干介面,spring使用JDK的java.lang.reflect.Proxy類代理。

          優點:因為有介面,所以使系統更加鬆耦合

          缺點:為每一個目標類建立介面

  2、若目標物件沒有實現任何介面,spring使用CGLIB庫生成目標物件的子類。

          優點:因為代理類與目標類是繼承關係,所以不需要有介面的存在。

          缺點:因為沒有使用介面,所以系統的耦合性沒有使用JDK的動態代理好。

Spring AOP原理

  1、當spring容器啟動的時候,載入兩個bean,對像個bean進行例項化
  2、當spring容器對配置檔案解析到<aop:config>的時候,把切入點表示式解析出來,按照切入點表示式匹配spring容器內容的bean
  3、如果匹配成功,則為該bean建立代理物件
  4、當客戶端利用context.getBean獲取一個物件時,如果該物件有代理物件,則返回代理物件,如果沒有代理物件,則返回物件本身

總結:

  如果加入容器的目標物件有實現介面,用JDK代理

  如果目標物件沒有實現介面,用Cglib代理   

  如果目標物件實現了介面,且強制使用cglib代理,則會使用cglib代理。

 侷限:

spring aop的切入點支援有限,而且對於static方法和final方法都無法支援aop(因為此類方法無法生成代理類);另外spring aop只支援對於ioc容器管理的bean,其他的普通java類無法支援aop。現在的spring整合了aspectj,在spring體系中可以使用aspectj語法來實現aop。

AspectJ

AspectJ是一個java實現的AOP框架,它能夠對java程式碼進行AOP編譯(一般在編譯期進行),讓java程式碼具有AspectJ的AOP功能(當然需要特殊的編譯器),可以這樣說AspectJ是目前實現AOP框架中最成熟,功能最豐富的語言,更幸運的是,AspectJ與java程式完全相容,幾乎是無縫關聯。

織入方式及其原理概要

AspectJ應用到java程式碼的過程(這個過程稱為織入),對於織入這個概念,可以簡單理解為aspect(切面)應用到目標函式(類)的過程。對於這個過程,一般分為動態織入和靜態織入,動態織入的方式是在執行時動態將要增強的程式碼織入到目標類中,這樣往往是通過動態代理技術完成的,如Java JDK的動態代理(Proxy,底層通過反射實現)或者CGLIB的動態代理(底層通過繼承實現),Spring AOP採用的就是基於執行時增強的代理技術,ApectJ採用的就是靜態織入的方式。ApectJ主要採用的是編譯期織入,在這個期間使用AspectJ的acj編譯器(類似javac)把aspect類編譯成class位元組碼後,在java目標類編譯時織入,即先編譯aspect類再編譯目標類。

AOP 術語

  • 通知: 
  • 定義:切面也需要完成工作。在 AOP 術語中,切面的工作被稱為通知。
  • 工作內容:通知定義了切面是什麼以及何時使用。除了描述切面要完成的工作,通知還解決何時執行這個工作。
  • Spring 切面可應用的 5 種通知型別:
  1. Before——在方法呼叫之前呼叫通知
  2. After——在方法完成之後呼叫通知,無論方法執行成功與否
  3. After-returning——在方法執行成功之後呼叫通知
  4. After-throwing——在方法丟擲異常後進行通知
  5. Around——通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為
  • 連線點: 
    • 定義:連線點是一個應用執行過程中能夠插入一個切面的點。
    • 連線點可以是呼叫方法時、丟擲異常時、甚至修改欄位時、
    • 切面程式碼可以利用這些點插入到應用的正規流程中
    • 程式執行過程中能夠應用通知的所有點。
  • 切點: 
    • 定義:如果通知定義了“什麼”和“何時”。那麼切點就定義了“何處”。切點會匹配通知所要織入的一個或者多個連線點。
    • 通常使用明確的類或者方法來指定這些切點。
    • 作用:定義通知被應用的位置(在哪些連線點)
  • 切面: 
    • 定義:切面是通知和切點的集合,通知和切點共同定義了切面的全部功能——它是什麼,在何時何處完成其功能。
  • 引入: 
    • 引入允許我們向現有的類中新增方法或屬性
  • 織入: 
    • 織入是將切面應用到目標物件來建立的代理物件過程。
    • 切面在指定的連線點被織入到目標物件中,在目標物件的生命週期中有多個點可以織入
  1. 編譯期——切面在目標類編譯時期被織入,這種方式需要特殊編譯器。AspectJ的織入編譯器就是以這種方式織入切面。
  2. 類載入期——切面在類載入到JVM ,這種方式需要特殊的類載入器,他可以在目標類被引入應用之前增強該目標類的位元組碼。AspectJ5 的 LTW 就支援這種織入方式
  3. 執行期——切面在應用執行期間的某個時刻被織入。一般情況下,在織入切面時候,AOP 容器會為目標物件動態的建立代理物件。Spring AOP 就是以這種方式織入切面。