1. 程式人生 > >Spring AOP簡介與底層實現機制——動態代理

Spring AOP簡介與底層實現機制——動態代理

AOP簡介

AOP (Aspect Oriented Programing) 稱為:面向切面程式設計,它是一種程式設計思想。AOP 是 OOP(面向物件程式設計 Object Oriented Programming)的思想延續

AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼的編寫方式(例如效能監視、事務管理、安全檢查、快取、日誌記錄等)

AOP核心思想

基於代理思想,對原來目標物件,建立代理物件,在不修改原物件程式碼情況下,通過代理物件,呼叫增強功能的程式碼,從而對原有業務方法進行增強

切面:需要代理一些方法和增強程式碼

AOP的應用場景

場景一:記錄日誌

場景二:監控方法執行時間 (監控效能)

場景三: 許可權控制

場景四: 快取優化 (第一次呼叫查詢資料庫,將查詢結果放入記憶體物件, 第二次呼叫, 直接從記憶體物件返回,不需要查詢資料庫 )

場景五: 事務管理 (呼叫方法前開啟事務, 呼叫方法後提交或者回滾、關閉事務 )

Spring AOP程式設計兩種方式

方式一:Spring AOP使用純Java實現,不需要專門的編譯過程和類載入器,在執行期通過代理方式向目標類植入增強程式碼(程式設計複雜,不推薦)

方式二:Spring 2.0 之後支援第三方 AOP框架(AspectJ ),實現另一種 AOP程式設計 (推薦)

AOP程式設計相關術語

1.Aspect(切面): 是通知和切入點的結合,通知和切入點共同定義了關於切面的全部內容---它的功能、在何時和何地完成其功能

2.joinpoint(連線點):所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點.

3.Pointcut(切入點):所謂切入點是指我們要對哪些joinpoint進行攔截的定義.通知定義了切面的”什麼”和”何時”,切入點就定義了”何地”.

4.Advice(通知、增強):所謂通知是指攔截到joinpoint之後所要做的事情就是通知.通知分為前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)

5.Target(目標物件):代理的目標物件

6.Weaving(織入):是指把切面應用到目標物件來建立新的代理物件的過程.切面在指定的連線點織入到目標物件

7.Introduction(引入)(不要求掌握):在不修改類程式碼的前提下, Introduction可以在執行期為類動態地新增一些方法或Field.

AOP程式設計底層實現機制

AOP 就是要對目標進行代理物件的建立, Spring AOP是基於動態代理的,分別基於兩種動態代理機制: JDK動態代理和CGLIB動態代理

方式一:JDK動態代理

JDK動態代理,針對目標物件的介面進行代理 ,動態生成介面的實現類 (必須有介面)

過程要點

1.必須對介面生成代理

2.採用Proxy物件,通過newProxyInstance方法為目標建立代理物件。

該方法接收三個引數 :

  (1)目標物件類載入器

  (2)目標物件實現的介面

  (3)代理後的處理程式InvocationHandler

3.實現InvocationHandler 介面中 invoke方法,在目標物件每個方法呼叫時,都會執行invoke

service層

//介面(表示代理的目標介面)
public interface ICustomerService {
    //儲存
    void save();
    //查詢
    int find();
}
//實現層
public class CustomerServiceImpl implements ICustomerService{

    @Override
    public void save() {
       System.out.println("客戶儲存了。。。。。");
    }

    @Override
    public int find() {
       System.out.println("客戶查詢數量了。。。。。");
       return 100;
    }
}

JDK動態代理工廠

//專門用來生成jdk的動態代理物件的-通用
public class JdkProxyFactory{
    //target目標物件
    private Object target;
    
    //注入target目標物件
    public JdkProxyFactory(Object target) {
       this.target = target;
    }

    public Object getProxyObject(){

        /**
        * 引數1:目標物件的類載入器
        * 引數2:目標物件實現的介面
        * 引數3:回撥方法物件
       */
       return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 
            new InvocationHandler(){
                public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
                    //如果是儲存的方法,執行記錄日誌操作
                    if(method.getName().equals("save")){
                        System.out.println("增強程式碼:寫日誌了。。。");
                     }
                    //目標物件原來的方法執行
                    Object object = method.invoke(target, args);//呼叫目標物件的某個方法,並且返回目標物件
                    return object;
                 }
        });
    }
}

測試方法

//目標:使用動態代理,對原來的方法進行功能增強,而無需更改原來的程式碼。
//JDK動態代理:基於介面的(物件的型別,必須實現介面!)
@Test
public void testJdkProxy(){
   //target(目標物件)
   ICustomerService target = new CustomerServiceImpl();
   //例項化注入目標物件
   JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
   //獲取 Object代理物件:基於目標物件型別的介面的型別的子型別的物件
   //必需使用介面物件去強轉
   ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();
   //呼叫目標物件的方法
   proxy.save();
   System.out.println("————————————————————");
   proxy.find();
}                                                                                  

注意

JDK動態代理產生的物件不再是原物件

  • 錯誤:
CustomerServiceImpl proxy = (CustomerServiceImpl)jdkProxyFactory.getProxyObject();                                
  • 正確
ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();

方式二:Cglib動態代理

Cglib的引入為了解決類的直接代理問題(生成代理子類),不需要介面也可以代理

該代理方式需要相應的jar包,但不需要匯入。因為Spring core包已經包含cglib ,而且同時包含了cglib 依賴的asm的包(動態位元組碼的操作類庫)

//沒有介面的類
public class ProductService {
    public void save() {
       System.out.println("商品儲存了。。。。。");
    }
    
    public int find() {
       System.out.println("商品查詢數量了。。。。。");
       return 99;
    }
}

使用cglib代理

//cglib動態代理工廠:用來生成cglib代理物件
public class CglibProxyFactory implements MethodInterceptor{
    private Object target;

    //注入代理物件
    public CglibProxyFactory(Object target) {
       this.target = target;
    }
    
    //獲取代理物件
    public Object getProxyObject(){
       //1.代理物件生成器(工廠思想)
       Enhancer enhancer = new Enhancer();
        // 類載入器
       enhancer.setClassLoader(target.getClass().getClassLoader());
       
       //2.在增強器上設定兩個屬性
       //設定要生成代理物件的目標物件:生成的目標物件型別的子型別
       enhancer.setSuperclass(target.getClass());
       //設定回撥方法
       enhancer.setCallback(this);
        
       //3.建立獲取物件
       return enhancer.create();
    }
    
    //回撥方法(代理物件的方法)
    /**
     *  引數1:代理物件
     *  引數2:目標物件的方法物件
     *  引數3:目標物件的方法的引數的值
     *  引數4:代理物件的方法物件
     */
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
       //如果是儲存的方法,執行記錄日誌操作
       if(method.getName().equals("save")){
            System.out.println("增強程式碼:寫日誌了。。。");
       }
        //目標物件原來的方法執行
        //呼叫目標物件的某個方法,並且返回目標物件
       Object object = method.invoke(target, args);
       return object;
    }
}

測試方法

//cglib動態代理:可以基於類(無需實現介面)生成代理物件
    @Test
    public void testCglibProxy(){
       //target目標:
       ProductService target = new ProductService();

       //代理工廠物件,注入目標
       CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
       
       //獲取proxy
       //代理物件,其實是目標物件型別的子型別
       ProductService proxy = (ProductService)cglibProxyFactory.getProxyObject();
       //呼叫代理物件的方法
       proxy.save();
       System.out.println("—————————————————————");
       proxy.find();
    }

總結

spring在執行期,生成動態代理物件,不需要特殊的編譯器

Spring AOP 優先對介面進行代理 (使用Jdk動態代理)如果目標物件沒有實現任何介面,才會對類進行代理 (使用cglib動態代理)

需要注意的

1.對介面建立代理優於對類建立代理,因為會產生更加鬆耦合的系統,所以spring預設是使用JDK代理。對類代理是讓遺留系統或無法實現介面的第三方類庫同樣可以得到通知,這種方式應該是備用方案

2.標記為final的方法不能夠被通知。spring是為目標類產生子類。任何需要被通知的方法都被複寫,將通知織入。final方法是不允許重寫的

3.spring只支援方法連線點:不提供屬性接入點,spring的觀點是屬性攔截破壞了封裝。面向物件的概念是物件自己處理工作,其他物件只能通過方法呼叫的得到的結