【spring基礎】AOP概念與動態代理詳解

1、代理模式java

代理模式的英文叫作Proxy或Surrogate,中文均可譯爲」代理「,所謂代理,就是一我的或者一個機構表明另外一我的或者另外一個機構採起行動。在一些狀況下,一個客戶不想或者不可以直接引用一個對象,而代理對象能夠在客戶端和目標對象之間起到中介的做用。程序員

以簡單模擬事務的執行過程說明各類代理區別spring

1.1 靜態代理express

由程序員建立或由特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。編程

public interface PersonDao {

    void savePerson();
}
public class PersonDaoImpl implements PersonDao {

    @Override
    public void savePerson() {
        System.out.println("save person");
    }
}
public class Transaction {
    
    void beginTransaction(){
        System.out.println("begin Transaction");
    }
    
    void commit(){
        System.out.println("commit");
    }
}

接下來編寫靜態代理類---實現PersonDao接口數組

/**
 * 靜態代理類
 * @author qjc
 */
public class PersonDaoProxy implements PersonDao{

    PersonDao personDao;
    Transaction transaction;
    
    public PersonDaoProxy(PersonDao personDao, Transaction transaction) {
        this.personDao = personDao;
        this.transaction = transaction;
    }

    @Override
    public void savePerson() {
        this.transaction.beginTransaction();
        this.personDao.savePerson();
        this.transaction.commit();
    }
}

測試app

/**
 * 測試靜態代理
 * @author qjc
 */
public class TestPersonProxy {
    
    @Test
    public void testSave(){
        PersonDao personDao = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        PersonDaoProxy proxy = new PersonDaoProxy(personDao, transaction);
        
        proxy.savePerson();
    }
}

總結:ide

  一、靜態代理模式並無作到事務的重用模塊化

  二、假設dao有100個類,100個proxy,接口中有多少方法,在proxy層就得實現多少方法,有多少方法就要開啓和提交多少事務工具

  三、若是一個proxy實現了多個接口,若是其中的一個接口發生變化(添加了一個方法),那麼proxy也要作相應改變

 

1.2 JDK動態代理

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

JDK的動態代理必須具有四個條件:一、目標接口 二、目標類 三、攔截器 四、代理類

 

使用上個例子的PersonDao接口、PersonDaoImpl類及Transaction類

編寫攔截器

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 攔截器 
 *         一、目標類導入進來 
 *         二、事物導入進來 
 *         三、invoke完成:開啓事務、調用目標對象的方法、事務提交
 * 
 * @author qjc
 */
public class Interceptor implements InvocationHandler {

    private Object target; // 目標類
    private Transaction transaction;

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

    /**
     * @param proxy 目標對象的代理類實例
     * @param method 對應於在代理實例上調用接口方法的Method實例
     * @param args 傳入到代理實例上方法參數值的對象數組
     * @return 方法的返回值,沒有返回值是null
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        String methodName = method.getName();
        if ("savePerson".equals(methodName)
                || "deletePerson".equals(methodName)
                || "updatePerson".equals(methodName)) {

            this.transaction.beginTransaction(); // 開啓事務
            method.invoke(target); // 調用目標方法
            this.transaction.commit(); // 提交事務

        } else {
            method.invoke(target);
        }
        return null;
    }
}

測試

/**
 * 測試jdk動態代理
 * @author qjc
 */
public class TestJDKProxy {
    
    @Test
    public void testSave(){
        /**
         * 一、建立一個目標對象
         * 二、建立一個事務
         * 三、建立一個攔截器
         * 四、動態產生一個代理對象
         */
        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();
    }
}

總結:

  一、由於利用JDKProxy生成的代理類實現了接口,因此目標類中全部的方法在代理類中都有。

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

       三、利用JDKProxy方式必須有接口的存在。

       四、invoke方法中的三個參數能夠訪問目標類的被調用方法的API、被調用方法的參數、被調用方法的返回類型。

缺點:

  一、在攔截器中除了能調用目標對象的目標方法之外,功能是比較單一的,在這個例子中只能處理事務
  二、攔截器中的invoke方法的if判斷語句在真實的開發環境下是不靠譜的,由於一旦方法不少if語句須要寫不少。

 

1.3 CGLIB動態代理

使用上個例子的PersonDaoImpl類和Transaction類(不用接口)

編寫攔截器類

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

/**
 * CGLIB代理 攔截器 
 * @author qjc
 */
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產生的代理對象,代理類是目標類的子類
 * @author qjc
 */
public class TestCglibProxy {
    
    @Test
    public void testSave(){
    
        Object target = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        Interceptor interceptor = new Interceptor(target, transaction);
        
        PersonDaoImpl personDaoImpl = (PersonDaoImpl) interceptor.createProxy();
        personDaoImpl.savePerson();
    }
}

總結:

  一、CGlib是一個強大的,高性能,高質量的Code生成類庫。它能夠在運行期擴展Java類與實現Java接口。

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

  三、用CGlib生成 代理類不須要接口

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

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

 

CGLIB和JDK動態代理區別:

  JDK:

        目標類和代理類實現了共同的接口

        攔截器必須實現InvocationHandler接口,而這個接口中invoke方法體的內容就是代理對象方法體的內容

  CGLIB:

        目標類 是代理類的父類

        攔截器必須實現MethodInterceptor接口,而接口中的intercept方法就是代理類的方法體,使用字節碼加強機制建立代理對象的.

 

2、面向切面編程

OOP(面向對象編程):封裝、繼承、多態、抽象

        封裝,對代碼進行基本的管理、模塊化的管理。每一個類可能都有本身的職能,出了問題就是論事找人就好了。從修改角度講,直接修改代碼可能有風險,這不是個長遠之計,最天然的是從類型封裝變化。可是新的類型和舊的體系之間怎麼去融合,因此說須要在類與類之間創建一種血緣關係。那麼這就是繼承的需求,經過繼承就能夠發現這些類之間是有關聯的,它們之間是有父子關係的。而後在繼承基礎之上多態起決定性的特徵。因此說通常認爲面向對象最核心的特徵實際上是多態前面幾個都是在作鋪墊的多態纔是它最核心的特徵子類中經過重寫方法表明了擴展這個層面的東西而它能融入老的體系中可以正常工做這是重用這個層面的東西新的方法舊的體系擴展和重用。

 

AOP(面向切面編程):

      面向切面編程,是一種經過預編譯方式運行期動態代理實如今不修改源代碼的狀況下給程序動態統一添加功能的一種技術.

 

OOPAOP區別:

  OOP:針對業務處理過程的實體及其屬性和行爲進行抽象封裝,以得到更加清楚的邏輯單元劃分。

   AOP:針對業務處理過程當中的橫切邏輯 進行提取,它所面對的是處理過程當中的某個步驟或者階段,以得到邏輯過程當中各部分之間低耦合的隔離效果。這兩種設計思想在目標上有着本質的差別。AOP作到了代碼塊的重用

 

spring AOP代理機制:

  一、若目標對象實現了若干接口,spring使用JDK的java.lang.reflect.Proxy類代理。

          優勢:由於有接口,因此使系統更加鬆耦合

          缺點:爲每個目標類建立接口

  二、若目標對象沒有實現任何接口,spring使用CGLIB庫生成目標對象的子類。

          優勢:由於代理類與目標類是繼承關係,因此不須要有接口的存在。

          缺點:由於沒有使用接口,因此係統的耦合性沒有使用JDK的動態代理好。

 

使用第一個例子的 PersonDao接口、PersonDaoImpl類和Transaction類

編寫spring配置

    <bean id="personDao" class="cn.qjc.aop.xml.PersonDaoImpl"></bean>
    <bean id="transaction" class="cn.qjc.aop.xml.Transaction"></bean>
    
    <aop:config>
        <!-- 切入點表達式  肯定目標類 -->
        <aop:pointcut expression="execution(* cn.qjc.aop.xml.PersonDaoImpl.*(..))" id="perform"/>
    
        <!-- ref指向對象就是切面 -->
        <aop:aspect ref="transaction">
            <aop:before method="beginTransaction" pointcut-ref="perform"/>
            <aop:after-returning method="commit" pointcut-ref="perform"/>
        </aop:aspect>
    </aop:config>
    
</beans>

測試

/**
 * 測試spring動態代理
 * @author qjc
 */
public class TransactionTest {

    @Test
    public void testSave(){
        ApplicationContext context = new ClassPathXmlApplicationContext("cn/qjc/aop/xml/applicationContext.xml");
        PersonDao personDao = (PersonDao) context.getBean("personDao");
        personDao.savePerson();
    }
}

 

spring AOP原理

  一、當spring容器啓動的時候,加載兩個bean,對像個bean進行實例化  二、當spring容器對配置文件解析到<aop:config>的時候,把切入點表達式解析出來,按照切入點表達式匹配spring容器內容的bean  三、若是匹配成功,則爲該bean建立代理對象  四、當客戶端利用context.getBean獲取一個對象時,若是該對象有代理對象,則返回代理對象,若是沒有代理對象,則返回對象自己

相關文章
相關標籤/搜索