1. 程式人生 > >spring AOP概念及代理模式

spring AOP概念及代理模式

spring AOP

代理模式的英文Proxy或Surrogate,中文都可譯為”代理“,代理的含義,就是邀請一個人或者一個機構代表另一個人或者另一個機構採取行動。

在一些情況下,一個客戶不想或者不能夠直接引用一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。

JAVA中的動態代理和靜態代理:

  • 靜態代理:編譯時將增強程式碼植入class檔案,因為是編譯期進行的增強,所以程式碼執行時效率比動態代理高。使用Aspect可以實現靜態代理。
  • 動態代理:執行時生成代理類並載入,效率比靜態代理要低,spring中使用了上文中的兩種動態代理的方式來實現代理類的生成。
  • 靜態代理

  由我們程式設計師建立或由特定工具自動生成原始碼,再對其編譯。在程式執行前,代理類的.class檔案就已經存在了。

1 package com.lhy.ssms.proxy.demo5;
2 
3 public interface PersonDao {
4     void addPerson();
5 }
1 public class PersonDaoImpl implements PersonDao {
2 
3     @Override
4     public void addPerson() {
5         System.out.println("add person to my Family");
6 } 7 }
 1 public class Transaction {
 2     
 3     void beginTransaction(){
 4         System.out.println("開始事務 begin Transaction");
 5     }
 6     
 7     void commit(){
 8         System.out.println(" 提交 commit");
 9     }
10 }

好的,我們開始編寫靜態代理類了 --  實現PersonDao介面

 1
/** 2 * 這個是靜態代理類 4 */ 5 public class PersonDaoProxy implements PersonDao{ 6 7 PersonDao personDao; 8 Transaction transaction; 9 10 public PersonDaoProxy(PersonDao personDao, Transaction transaction) { 11 this.personDao = personDao; 12 this.transaction = transaction; 13 } 14 15 @Override 16 public void addPerson() { 17 this.transaction.beginTransaction(); 18 this.personDao.addPerson(); 19 this.transaction.commit(); 20 } 21 }

最後,我們需要 測試

/**
 * 測試我們前面寫的靜態代理*/
public class TestPersonProxy {
    
    @Test
    public void testAdd(){
        PersonDao personDao = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        PersonDaoProxy proxy = new PersonDaoProxy(personDao, transaction);
        
        proxy.AddPerson();
    }
}

總結:

  1、靜態代理模式並沒有做到事務的重用

  2、假設dao有100個類,100個proxy,介面中有多少方法,在proxy層就得實現多少方法,有多少方法就要開啟和提交多少事務

  3、如果一個proxy實現了多個介面,如果其中的一個介面發生變化(添加了一個方法),那麼proxy也要做相應改動。

 

  •  JDK動態代理

  採用java內建的代理API實現

  動態代理類:在程式執行時,運用反射機制動態建立而成。

  JDK的動態代理必須具備四個條件:1、目標介面 2、目標類 3、攔截器 4、代理類

 1 import java.lang.reflect.InvocationHandler;
 2 import java.lang.reflect.Method;
 3 
 4 /**
 5  * 攔截器 
 6  *         1、目標類匯入進來 
 7  *         2、事物匯入進來 
 8  *         3、invoke完成:開啟事務、呼叫目標物件的方法、事務提交
 9  */
10 public class Interceptor implements InvocationHandler {
11 
12     private Object target; // 目標類
13     private Transaction transaction;
14 
15     public Interceptor(Object target, Transaction transaction) {
16         this.target = target;
17         this.transaction = transaction;
18     }
19 
20     /**
21      * @param proxy 目標物件的代理類例項
22      * @param method 對應於在代理例項上呼叫介面方法的Method例項
23      * @param args 傳入到代理例項上方法引數值的物件陣列
24      * @return 方法的返回值,沒有返回值是null
25      * @throws Throwable
26      */
27     public Object invoke(Object proxy, Method method, Object[] args)
28             throws Throwable {
29         String methodName = method.getName();
30         if ("addPerson".equals(methodName)
31                 || "deletePerson".equals(methodName)
32                 || "updatePerson".equals(methodName)) {
33 
34             this.transaction.beginTransaction(); // 開啟事務
35             method.invoke(target); // 呼叫目標方法
36             this.transaction.commit(); // 提交事務
37 
38         } else {
39             method.invoke(target);
40         }
41         return null;
42     }
43 }

  進入測試:

/**
 * 測試jdk動態代理
 */
public class TestJDKProxy {
    
    @Test
    public void testAdd(){
        /**
         * 1、建立一個目標物件
         * 2、建立一個事務
         * 3、建立一個攔截器
         * 4、動態產生一個代理物件
         */
        Object target = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        Interceptor interceptor = new Interceptor(target, transaction);
        /**
         * 引數一:設定程式碼使用的類載入器,一般採用跟目標類相同的類載入器
         * 引數二:設定代理類實現的介面,跟目標類使用相同的介面
         * 引數三:設定回撥物件,當代理物件的方法被呼叫時,會呼叫該引數指定物件的invoke方法
         */
        PersonDao personDao = (PersonDao) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                interceptor);
        personDao.savePerson();
    }
}

總結:

  1、因為利用JDKProxy生成的代理類實現了介面,所以目標類中所有的方法在代理類中都有。

  2、生成的代理類的所有的方法都攔截了目標類的所有的方法。而攔截器中invoke方法的內容正好就是代理類的各個方法的組成體。

       3、利用JDKProxy方式必須有介面的存在。

       4、invoke方法中的三個引數可以訪問目標類的被呼叫方法的API、被呼叫方法的引數、被呼叫方法的返回型別。

缺點:

  1、在攔截器中除了能呼叫目標物件的目標方法以外,功能是比較單一的,在這個例子中只能處理事務
  2、攔截器中的invoke方法的if判斷語句在真實的開發環境下是不靠譜的,因為一旦方法很多if語句需要寫很多

 

  •  CGLIB動態代理:採用第三方API實現

  JDK中的代理模式必須要有介面,所以就出現了CGLIB。

  使用上個例子的PersonDaoImpl類和Transaction類(不用介面)

  

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

/**
 * CGLIB代理 攔截器 
 */
public class Interceptor  implements MethodInterceptor {

    private Object target; // 代理的目標類
    private Transaction transaction;

    public Interceptor(Object target, Transaction transaction) {
        this.target = target;
        this.transaction = transaction;
    }

    /**
     * 建立目標物件的代理物件
     * 
     * @return
     */
    public Object createProxy() {
        // 程式碼增強
        Enhancer enhancer = new Enhancer(); // 該類用於生成代理物件
        enhancer.setCallback(this); // 引數為攔截器
        enhancer.setSuperclass(target.getClass());// 設定父類
        return enhancer.create(); // 建立代理物件
    }

    /**
     * @param obj 目標物件代理類的例項
     * @param method 代理例項上 呼叫父類方法的Method例項
     * @param args 傳入到代理例項上方法引數值的物件陣列
     * @param methodProxy 使用它呼叫父類的方法
     * @return
     * @throws Throwable
     */
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {
        this.transaction.beginTransaction();
        method.invoke(target);
        this.transaction.commit();
        return null;
    }
}
/**
 * 測試cglib動態代理
 * 通過cglib產生的代理物件,代理類是目標類的子類
 */
public class TestCglibProxy {
    
    @Test
    public void testAdd(){
    
        Object target = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        Interceptor interceptor = new Interceptor(target, transaction);
        
        PersonDaoImpl personDaoImpl = (PersonDaoImpl) interceptor.createProxy();
        personDaoImpl.addPerson();
    }
}

總結:

  1、CGlib是一個強大的,高效能,高質量的Code生成類庫。它可以在執行期擴充套件Java類與實現Java介面。

  2、用CGlib生成代理類是目標類的子類。

  3、用CGlib生成 代理類不需要介面

  4、用CGLib生成的代理類重寫了父類的各個方法。

  5、攔截器中的intercept方法內容正好就是代理類中的方法體

 

CGLIB和JDK動態代理區別:

  JDK:

        目標類和代理類實現了共同的介面

        攔截器必須實現InvocationHandler介面,而這個介面中invoke方法體的內容就是代理物件方法體的內容

  CGLIB:

        目標類 是代理類的父類

        攔截器必須實現MethodInterceptor介面,而介面中的intercept方法就是代理類的方法體,使用位元組碼增強機制建立代理物件的.

 

什麼是AOP?

 AOP(面向切面程式設計):

      面向切面程式設計,是一種通過預編譯方式執行期動態代理實現在不修改原始碼的情況下給程式動態統一新增功能的一種技術.

 

spring AOP代理機制:

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

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

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

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

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

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

 

每一種代理都有各自的優缺點,根據需求挑選合適的方法。