動態代理模式 -必須要掌握

動態代理模式 -必須要掌握

   點關注不迷路,歡迎再訪!		

精簡博客內容,儘量已行業術語來分享。
努力做到對每一位認可自己的讀者負責。
幫助別人的同時更是豐富自己的良機。

此文章參考於:《JavaEE互聯網輕量級框架整合開發》

動態代理的意義在於生成一個佔位(又稱代理對象),來代理真實對象,從而控制真實對象的訪問。

一.代理基本概念
先來談談什麼是代理模式。假設場景,你是一位軟件工程師。客戶帶着需求去找公司顯然不會直接和你談,而是去找商務談,此時客戶會認爲商務就代表公司。
讓我們用一張圖來表示代理模式的含義,如圖
在這裏插入圖片描述
代理的作用就是,在真實對象訪問之前或之後加入對應的邏輯,或者根據其他規則控制是否使用真實對象。顯然如圖例子裏商務控制了客戶對軟件工程師的訪問。
經過上面的論述,我們知道商務和軟件工程師是代理和被代理的關係,客戶是通過商務去訪問軟件工程師的。此時客戶就是程序中的調用者,商務就是代理對象,軟件工程師就是真實對象。我們需要在調用者調用對象之前產生一個代理對象,而這個代理對象需要和真實對象建立代理關係,所以代理必須分爲兩步驟:
1.代理對象和真實對象建立代理關係。
2.實現代理對象的代理邏輯方法。

二.代理基本概念

JAVA中有多種動態代理技術,不如JDK、CGLIB、Javassist、ASM,目前Spring常用的動態代理技術爲JDK和CGLIB,而Mybitas還使用了Javassist,無論哪種代理其技術,它們的理念都是相似的。

2.1JDK動態代理
JDK動態代理是java,lang.reflect.*包提供的方式,它必須藉助一個接口才能產生代理對象,所以先定義接口,如代碼:

public interface HelloWorld{
   public  void  sayHelloWorld();
}
public class HelloWorldImpl implements HelloWorld{

	@Override
	public void sayHelloWorld() {
		System.out.println("Hello World");
	}
}

上面提供了簡單的java接口和實現關係,此時可以開始動態代理了。按照之前的分析,先要建立起代理對象和真實對象的關係,然後實現代理邏輯,所以一共分爲兩步。在JDK動態代理中,要實現代理邏輯類必須去實現java.lang.reflect.InvocationHandler接口,它裏面定義了一個invoke方法,並提供接口數組用於下掛代理對象,如下代碼:

public class HelloWorldImpl implements HelloWorld{

	@Override
	public void sayHelloWorld() {
		System.out.println("Hello World");
	}
}

動態代理綁定和代理邏輯實現

public class JdkProxyExample implements InvocationHandler{
	
	//真實對象
	private Object target = null;
	
	/**
	 * 建立代理對象和真實對象的代理關係,並返回代理對象
	 * @param target 真實對象
	 * @return 代理對象
	 */
	public Object bind(Object target) {
		this.target = target;
		return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);
	}
	
	
	/**
	  * 代理方法邏輯
	  * @param proxy 代理對象
	  * @param method 當前調度方法
	  * @param args 當前方法參數
	  * @return 代理結果返回
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("進入代理邏輯方法");
		System.out.println("在調度真實對象之前的服務");
		Object obj = method.invoke(target, args);  //相當與調用sayHelloWorld()
		System.out.println("在調度真實對象之後的服務");
		return obj;
	}
}

第一步,建立代理對象和真實對象的關係。這裏使用了bind方法去完成的,方法裏面首先用類的屬性target保存了真實對象。
其中newProxyInstance方法包含3個參數:
1.類加載器,採用了target本身的類加載器。
2.把生成的動態代理對象下掛在哪些接口下,以上寫法就是放在target實現的接口下。
3.定義實現方法邏輯的代理類,this代表當前對象,它必須實現InvocationHandler接口的invoke

第二步,實現代理邏輯方法。invoke可以實現代理邏輯,invoke方法的3個參數的含義如下所示:
1.proxy,代理對象,就是bind方法生成的對象。
2.method, 當前調度的方法。
3.args,調度方法的參數。
當我們使用了代理對象調度方法後,它就會進入到invoke方法裏面。
Object obj = method.invoke(target, args);
這行代碼相當於調度真實對象的方法,只是通過反射實現而已。

下面就讓我們進入測試JDK動態代理。代碼如下:

public class jdkProxyTest {
	public static void main(String[] args) {
		JdkProxyExample jdk = new JdkProxyExample();
		//綁定關係,因爲掛在接口HelloWorld下,所以聲明代理對象proxy
		HelloWorld proxy=(HelloWorld) jdk.bind(new HelloWorldImpl());
		//注意,此時HelloWorld對象已經是一個代理對象,它會進入代理的邏輯方法invoke裏
		proxy.sayHelloWorld();
	}
}

首先通過bind方法綁定了代理關係,然後在代理對象調度sayHelloWorld方法是進入了代理的邏輯,測試結果如下:


進入代理邏輯方法
在調度真實對象之前的服務
Hello World
在調度真實對象之後的服務


此時,在調度打印Hello World之前和之後都可以加入相關的邏輯。
以上就是JDK動態代理,他是一直最爲常用的動態代理,十分重要,後面會以JDK動態代理爲主討論框架的實現。代理模式掌握不易,起初可以通過打斷點,一步步驗證執行步驟,就一定能夠掌握好它。

2.2CGLIB動態代理
JDK動態代理必須通過接口才能使用,在一些不能提供接口的環境中,只能採用其他第三方技術,比如CGLIB動態代理。他的優勢在於不需要提供接口,只要一個非抽象類就能實現動態代理。

首先,我們需要在工程的POM文件中引入cglib的dependency,這裏我們使用的是2.2.2版本

		<dependency>
           <groupId>cglib</groupId>
           <artifactId>cglib</artifactId>
           <version>2.2.2</version>
        </dependency>

以ReflectServiceImpl爲例,不存在實現任何接口,所以不可以使用JDK動態代理,這裏採用CGLIB動態代理。

public class ReflectServiceImpl {
	public void sayHello(String name) {
		System.out.println("Hello "+name);
	}
}

CGLIB動態代理

public class CglibProxyExample implements MethodInterceptor{

	/**
	   * 生成CGLIB代理對象
	 * @param cls  -Class類
	 * @return  Class類的CGLIB代理對象
	 */
	public Object getProxy(Class cls) {
		//增強類對象
		Enhancer enhancer=new Enhancer();
		//設置增強類型
		enhancer.setSuperclass(cls);
		//定義代理邏輯對象爲當前對象,要求當前對象實現MethodInterceptor()
		enhancer.setCallback(this);
		//生成並返回代理對象
		return enhancer.create();
	}
	
	
	/**
	       * 代理邏輯方法
	   * @param proxy 代理對象
	   * @param method 方法
	   * @param args  方法參數
	   * @param methodProxy 方法代理
	   * @return 代理邏輯返回
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		System.out.println("在調度真實對象之前");
		//CGLIB反射調用真實對象方法
		Object result = methodProxy.invokeSuper(proxy, args);
		System.out.println("在調度真實對象之後");
		return result;
	}
}

這裏用了CGLIB的加強者Enhancer,通過設置超類的方法(setSuperclass),然後通過setCallback方法設置哪個類爲它的代理類。其中,參數爲this就意味着是當前對象,那就要求用this這個對象實現接口MethodInterceptor的方法——intercept
然後返回代理對象。

下面測試一下CGLIB動態代理,如代碼:

public class testCGLIBProxy {	
   public static void main(String[] args) {
	   CglibProxyExample cpe = new CglibProxyExample();
	   ReflectServiceImpl obj = (ReflectServiceImpl) cpe.getProxy(ReflectServiceImpl.class);
	   obj.sayHello("andy");
   }
}

在調度真實對象之前
Hello andy
在調度真實對象之後


掌握了JDK動態代理就很容易掌握CGLIB動態代理,因爲二者都是通過getProxy()生成代理對象,制定代理的邏輯。而代理邏輯類要實現一個接口的一個方法,那麼這個接口定義方法就是代理邏輯方法,它可以控制真實對象的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章