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的觀點是屬性攔截破壞了封裝。面向對象的概念是對象自己處理工作,其他對象只能通過方法調用的得到的結果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM