深刻理解Spring AOP的動態代理

1. Spring AOP

Spring是一個輕型容器,Spring整個系列的最最核心的概念當屬IoC、AOP。可見AOP是Spring框架中的核心之一,在應用中具備很是重要的做用,也是Spring其餘組件的基礎。AOP(Aspect Oriented Programming),即面向切面編程,能夠說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來創建一種對象層次結構,用於模擬公共行爲的一個集合。不過OOP容許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。java

關於AOP的基礎知識,並非本文的重點,咱們主要來看下AOP的核心功能的底層實現機制:動態代理的實現原理。AOP的攔截功能是由java中的動態代理來實現的。在目標類的基礎上增長切面邏輯,生成加強的目標類(該切面邏輯或者在目標類函數執行以前,或者目標類函數執行以後,或者在目標類函數拋出異常時候執行。不一樣的切入時機對應不一樣的Interceptor的種類,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。編程

那麼動態代理是如何實現將切面邏輯(advise)織入到目標類方法中去的呢?下面咱們就來詳細介紹並實現AOP中用到的兩種動態代理。緩存

AOP的源碼中用到了兩種動態代理來實現攔截切入功能:jdk動態代理和cglib動態代理。兩種方法同時存在,各有優劣。jdk動態代理是由java內部的反射機制來實現的,cglib動態代理底層則是藉助asm來實現的。總的來講,反射機制在生成類的過程當中比較高效,而asm在生成類以後的相關執行過程當中比較高效(能夠經過將asm生成的類進行緩存,這樣解決asm生成類過程低效問題)。bash

下面咱們分別來示例實現這兩種方法。微信

2. JDK動態代理

2.1 定義接口與實現類

public interface OrderService {
    public void save(UUID orderId, String name);

    public void update(UUID orderId, String name);

    public String getByName(String name);
}
複製代碼

上面代碼定義了一個被攔截對象接口,即橫切關注點。下面代碼實現被攔截對象接口。框架

public class OrderServiceImpl implements OrderService {

    private String user = null;

    public OrderServiceImpl() {
    }

    public OrderServiceImpl(String user) {
        this.setUser(user);
    }

	//...
	
    @Override
    public void save(UUID orderId, String name) {
        System.out.println("call save()方法,save:" + name);
    }

    @Override
    public void update(UUID orderId, String name) {
        System.out.println("call update()方法");
    }

    @Override
    public String getByName(String name) {
        System.out.println("call getByName()方法");
        return "aoho";
    }
}
複製代碼

2.2 JDK動態代理類

public class JDKProxy implements InvocationHandler {
	//須要代理的目標對象
    private Object targetObject;
    
    public Object createProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
        return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
                this.targetObject.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	//被代理對象
        OrderServiceImpl bean = (OrderServiceImpl) this.targetObject;
        Object result = null;
        //切面邏輯(advise),此處是在目標類代碼執行以前
        System.out.println("---before invoke----");
        if (bean.getUser() != null) {
            result = method.invoke(targetObject, args);
        }
        System.out.println("---after invoke----");
        return result;
    }

	//...

}
複製代碼

上述代碼實現了動態代理類JDKProxy,實現InvocationHandler接口,而且實現接口中的invoke方法。當客戶端調用代理對象的業務方法時,代理對象執行invoke方法,invoke方法把調用委派給targetObject,至關於調用目標對象的方法,在invoke方法委派前判斷權限,實現方法的攔截。dom

2.3 測試

public class AOPTest {
    public static void main(String[] args) {
        JDKProxy factory = new JDKProxy();
        //Proxy爲InvocationHandler實現類動態建立一個符合某一接口的代理實例 
        OrderService orderService = (OrderService) factory.createProxyInstance(new OrderServiceImpl("aoho"));
		//由動態生成的代理對象來orderService 代理執行程序
        orderService.save(UUID.randomUUID(), "aoho");
    }

}
複製代碼

結果以下:ide

---before invoke----
call save()方法,save:aoho
---after invoke----
複製代碼

3. CGLIB字節碼生成

3.1 要代理的類

CGLIB既能夠對接口的類生成代理,也能夠針對類生成代理。示例中,實現對類的代理。函數

public class OrderManager {
    private String user = null;

    public OrderManager() {
    }

    public OrderManager(String user) {
        this.setUser(user);
    }

	//...

    public void save(UUID orderId, String name) {
        System.out.println("call save()方法,save:" + name);
    }

    public void update(UUID orderId, String name) {
        System.out.println("call update()方法");
    }

    public String getByName(String name) {
        System.out.println("call getByName()方法");
        return "aoho";
    }
}
複製代碼

該類的實現和上面的接口實現同樣,爲了保持統一。測試

3.2 CGLIB動態代理類

public class CGLibProxy implements MethodInterceptor {
    	// CGLib須要代理的目標對象
    	private Object targetObject;

       public Object createProxyObject(Object obj) {
        this.targetObject = obj;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        //回調方法的參數爲代理類對象CglibProxy,最後加強目標類調用的是代理類對象CglibProxy中的intercept方法 
        enhancer.setCallback(this);
        //加強後的目標類
        Object proxyObj = enhancer.create();
        // 返回代理對象
        return proxyObj;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object obj = null;
        //切面邏輯(advise),此處是在目標類代碼執行以前
        System.out.println("---before intercept----");
        obj = method.invoke(targetObject, args);
        System.out.println("---after intercept----");
        return obj;
    }
}
複製代碼

上述實現了建立子類的方法與代理的方法。getProxy(SuperClass.class)方法經過入參即父類的字節碼,擴展父類的class來建立代理對象。intercept()方法攔截全部目標類方法的調用,obj表示目標類的實例,method爲目標類方法的反射對象,args爲方法的動態入參,methodProxy爲代理類實例。method.invoke(targetObject, args)經過代理類調用父類中的方法。

3.3 測試

public class AOPTest {
    public static void main(String[] args) {
        OrderManager order = (OrderManager) new CGLibProxy().createProxyObject(new OrderManager("aoho"));
        order.save(UUID.randomUUID(), "aoho");
    }
複製代碼

結果以下:

---before intercept----
call save()方法,save:aoho
---after intercept----
複製代碼

4. 總結

本文主要講了Spring Aop動態代理實現的兩種方式,並分別介紹了其優缺點。jdk動態代理的應用前提是目標類基於統一的接口。若是沒有該前提,jdk動態代理不能應用。由此能夠看出,jdk動態代理有必定的侷限性,cglib這種第三方類庫實現的動態代理應用更加普遍,且在效率上更有優點。

JDK動態代理機制是委託機制,不須要以來第三方的庫,只要要JDK環境就能夠進行代理,動態實現接口類,在動態生成的實現類裏面委託爲hanlder去調用原始實現類方法;CGLib 必須依賴於CGLib的類庫,使用的是繼承機制,是被代理類和代理類繼承的關係,因此代理類是能夠賦值給被代理類的,若是被代理類有接口,那麼代理類也能夠賦值給接口。

訂閱最新文章,歡迎關注個人公衆號

微信公衆號

參考

  1. jdk動態代理代理與cglib代理原理探究
  2. AOP的底層實現-CGLIB動態代理和JDK動態代理
相關文章
相關標籤/搜索