AOP概述及實現原理

AOP

引言

Spring 爲解耦而生,其中AOP(面向切面編程)是很濃重的一筆。AOP(Aspect-Oriented-Programming)指一種程序設計範型,該範型以一種稱爲切面(aspect)的語言構造爲基礎,切面是一種新的模塊化機制,用來描述分散在對象、類或方法中的橫切關注點(crosscutting)。html

AOP相關概念

  • 鏈接點(JoinPoint):程序執行到某個特定位置(如:某個方法調用前,調用後,方法拋出異常),一個類或一段程序代碼擁有一些具備邊界性質的特定點,這些代碼中的特定點就是鏈接點。
  • 切點(PointCut):若是鏈接點至關於數據中的記錄,那麼切點就至關於查詢條件,一個切點能夠匹配多個鏈接點。SpringAop的規則解析引擎負責解析切點所設定的查詢條件,找到對應的鏈接點。
  • 加強(Advice):加強是織入到目標類鏈接點上的一段程序代碼,Spring提供的加強接口都是帶方位的,如BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。,不少資料上將加強譯爲「通知」,這明顯是詞不達意的。

說明:Advice在國內不少的書面資料上都被翻譯爲「通知」,但這個翻譯沒法表達其本質,有少許的讀物將這個詞翻譯爲「加強」,這個翻譯是比較爲準確的詮釋,咱們經過AOP將橫切關注功能加到原有的業務邏輯上,這就是對原有的業務邏輯進行的一種加強,這個加強能夠是前置加強,後置加強,返回後加強,拋出異常時加強,和包圍型加強。java

  • 引介(Introduction):引介是一種特殊的加強,他爲類添加一些屬性和方法,這樣,即便一個業務類本來沒有實現某個接口,經過引介功能,能夠動態的爲該業務類添加接口的實現邏輯,讓業務類稱爲這個接口的實現類
  • 織入(Weaving):織入是將加強添加到目標類具體鏈接點上的過程,AOP由三種織入方式:1 編譯期織入:須要特殊的Java編譯期(例如Aspect的ajc);2 裝載期織入:要求使用特殊的類加載器,在裝載類的時候對類進行加強,3 運行時織入:在運行時爲目標類生成代理實現加強。Spring採用動態代理的方式事實現了運行時織入,而AspectJ採用了編譯期織入和裝載期織入的方式。
  • 切面(Aspect):切面是由切點和加強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對鏈接點的定義。

原理

AOP是運用了動態代理。代理模式是GoF提出的23種設計模式中最爲經典的模式之一,代理是對象的結構模式,他給某一個對象提供一個對象代理,並由代理控制對原對象的引用,簡單的說,代理對象能夠完成比原對象更多的職責,當須要爲原對象添加橫切關注功能時,就可使用原對象的代理對象。咱們在打開Office系列的Word文檔時,若是文檔中有插圖,當文檔剛加載時,文檔中的插圖都只是一個虛框的佔位符,等真正用戶翻到某一頁要查看該圖片時,纔會真正加載這張圖片,這其實就是代理模式的使用,代替真正的虛框就是一個虛擬代理,Hibernate的load方法也是返回一個虛擬代理對象,等真正用戶須要訪問對象的屬性時,才向數據庫發出SQL語句。程序員

按照代理建立初期,代理能夠分爲兩種:面試

  • 靜態代理:由程序員建立或特定工具自動生成源代碼,再對其編譯,在程序運行前,代理類的.class文件就已經存在了。
  • 動態代理:在程序運行時,運行反射機制動態建立而成 另外還有CGLib動態代理,它是針對類實現的代理。

實現

JDK代理

靜態代理

下面用一個找槍手代考的例子演示代理模式的使用數據庫

package com.test.test2.interfaces;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/4/25 10:35
 */
public interface Candidate {
    public void answerTheQuestion();
}

複製代碼
package com.test.test2;

import com.test.test2.interfaces.Candidate;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/4/25 10:36
 */
public class LazyStudent implements Candidate {
    private String name;

    public LazyStudent(String name) {
        this.name = name;
    }

    @Override
    public void answerTheQuestion() {
        // 懶學生只能寫出本身名字不會答題
        System.out.println("姓名" + name);
    }
}

複製代碼
package com.test.test2;

import com.test.test2.interfaces.Candidate;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/4/25 10:40
 */
public class Gunman implements Candidate {
    private Candidate target;  //被代理的對象

    public Gunman(Candidate target) {
        this.target = target;
    }

    @Override
    public void answerTheQuestion() {
        // 搶手要寫上代考學生的名字
        target.answerTheQuestion();
        // 搶手幫助懶學生答題並交卷
        System.out.println("奮筆疾書書寫正確答案");
        System.out.println("交卷");
    }
}

複製代碼
@Test
public void testGunman(){
    Candidate c = new Gunman(new LazyStudent("王小二"));
    c.answerTheQuestion();
}
複製代碼

觀察代理能夠發現每個代理類只能爲一個接口服務,這樣一來程序開發中必然會產生不少的代理,並且,全部代理除了調用方法不同以外,其餘操做都同樣,則此時確定是重複代碼,解決這一個問題最好的作法是經過一個代理類完成所有的代理功能,那麼此時就必須使用動態代理了。編程

動態代理

從JDK 1.3 開始,Java提供了動態技術,動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。動態代理類不只簡化了編程工做,並且提升了軟件系統的可擴展性,由於反射機制能夠生成任意類型的動態代理。設計模式

java.lang.reflect包中的Proxy類和InvocationHandler接口提供了生成動態代理的能力。數組

JDK動態代理中包含一個接口和一個類:bash

  • InvocationHandler接口
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
複製代碼

參數說明:markdown

參數 說明
Object proxy 指被代理的對象
Method method 要調用的方法
Object[] args 方法調用時的參數

能夠將InvocationHandler接口的子類想象成一個代理的最終操做類,替換掉ProxySubject。

  • Proxy類:
  • Proxy類是專門完成代理的操做類,能夠經過此類爲一個或多個接口動態生成實現類,此類提供了以下的操做方法
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        /**************
        *具體內容略去**
        **************/
    }
複製代碼

參數說明:

源碼中的參數說明

* @param   loader the class loader to define the proxy class
* @param   interfaces the list of interfaces for the proxy class to implement
* @param   h the invocation handler to dispatch method invocations to
複製代碼
參數 說明
ClassLoader 類加載器
Class<?>[] interfaces 獲得所有的接口
InvocationHandler h 獲得InvocationHandler接口的子類實例

Ps:在Proxy中類的newProxyInstance()方法中須要一個ClassLoader類的實例,ClassLoader實際上對應的是類加載器。在Java中主要有三種類加載器:

  • Bootstrap ClassLoader:此口加載器採用C++編寫,通常開發中是看不到的。
  • Extendsion ClassLoader:用來進行擴展類的加載器,通常對應的是jre\lib\ext目錄中的類;
  • APPClassLoader:(默認)加載classpath制定的類,是最常使用的一種加載器。

BookFacade.java

public interface BookFacade {   
    public void addBook();  
}  
複製代碼

BookFacadeImpl.java

import net.battier.dao.BookFacade;  
  
public class BookFacadeImpl implements BookFacade {  
  
    @Override  
    public void addBook() {  
        System.out.println("增長圖書方法。。。");  
    }  
}
複製代碼

BookFacadeProxy.java

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
/** 
 * JDK動態代理代理類 
 */  
public class BookFacadeProxy implements InvocationHandler {  
    private Object target;  
    /** 
     * 綁定委託對象並返回一個代理類 
     * @param target 
     * @return 
     */  
    public Object bind(Object target) {  
        this.target = target;  
        //取得代理對象  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), this);   //要綁定接口(這是一個缺陷,cglib彌補了這一缺陷)  
    }  
  
    @Override  
    /** 
     * 調用方法 
     */  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable {  
        Object result=null;  
        System.out.println("事物開始");  
        //執行方法  
        result=method.invoke(target, args);  
        System.out.println("事物結束");  
        return result;  
    }  
}
複製代碼

TestProxy.java

import net.battier.dao.BookFacade;  
import net.battier.dao.impl.BookFacadeImpl;  
import net.battier.proxy.BookFacadeProxy;  
  
public class TestProxy {  
    public static void main(String[] args) {  
        BookFacadeProxy proxy = new BookFacadeProxy();  
        BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());  
        bookProxy.addBook();  
    }    
}
複製代碼

說明:使用Java的動態代理有一個侷限性就是被代理的類必須實現接口(也就是說只能對該類所實現接口中定義的方法進行代理),雖然面向接口編程是每一個優秀的Java程序員都知道的規則,但現實每每不盡人意,對於沒有實現接口的代理類如何爲其生成代理類呢?繼承繼承是最經典的擴展已有代碼能力的手段。CGLib代理就是這樣實現代理。

CGLib動態代理

CGLib採用很是底層的字節碼生成技術,經過爲一個類建立子類來生成代理類,它彌補了Java動態代理的不足,所以,Spring中的動態代理和CGLib都是建立代理的重要手段,對於實現了接口的類就用動態代理爲其生成代理類,而沒有實現接口的類就用CGLib經過繼承方式爲其建立代理類。但由於採用的是繼承,因此不能對final修飾的類進行代理。

intercept參數列表:

參數 說明
Object obj 代理的對象
Metho method 表示攔截的方法
Object[] args 數組表示參數列表,基本數據類型須要傳入其包裝類型,如int-->Integer、long-Long、double-->Double
MethodProxy methodProxy 表示對方法的代理,invokeSuper方法表示對被代理對象方法的調用
package com.test.test2.proxy;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/5/3 21:32
 */
public class BookFacadeImpl1 {
    public void addBook(){
        System.out.println("增長圖書的普通方法");
    }
}
複製代碼
package com.test.test2.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * @Author: Young
 * @QQ: 403353323
 * @Date: 2019/5/3 21:34
 */

/**
 * CGLib建立動態代理
 */
public class BookFacadeProxy implements MethodInterceptor {

    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        // 回調方法
        enhancer.setCallback(this);
        //建立代理對象
        return enhancer.create();
    }

    @Override
    // 回調方法  
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("開始事務");
        methodProxy.invokeSuper(obj, args);
        System.out.println("事務結束");
        return null;
    }
}

複製代碼
public class TestCglib {  
      
    public static void main(String[] args) {  
        BookFacadeCglib cglib=new BookFacadeCglib();  
        BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());  
        bookCglib.addBook();  
    }  
}
複製代碼
參考

AOP實現原理
Java面試知識點總結及解析
java動態代理(JDK和cglib)
Cglib及其基本使用

相關文章
相關標籤/搜索