Java面向對象系列[v1.0.0][使用反射生成動態代理]

在Java的java.lang.reflect包裏有個Proxy類和一個InvocationHandler接口,通過使用他們可以生成JDK動態代理類或動態代理對象

使用Proxy和InvocationHandler創建動態代理

Proxy提供了用於創建動態代理類和代理對象的靜態方法,他也是所有動態代理類的父類,如果在程序中爲一個或多個接口動態的生成實現類,就可以使用Proxy來創建動態代理類,如果需要爲一個或多個接口動態的創建實例,也可以使用Proxy來創建動態代理實例
Proxy提供了兩個方法來創建動態代理類和動態代理實例

  • static Class<?> getProxyClass(ClassLoader loader, Class<?>…interfaces):創建一個動態代理類所對應的Class對象,該代理類將實現interfaces所指定的多個接口,第一個ClassLoader參數指定生成動態代理類的類加載器
  • static Object new ProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):直接創建一個動態代理對象,該代理對象的實現類實現了interfaces指定的系列接口,執行代理對象的每個方法時都會被替換執行InvocationHandler對象的invoke方法
    即使採用第一個方法生成動態代理類之後,如果程序需要通過該代理類來創建對象,依然需要傳入一個InvocationHandler對象,也就是說系統生成的每個代理對象都有一個與之關聯的InvocationHandler對象

動態代理類創建動態代理對象

程序中可以採用先生成一個動態代理類,然後通過動態代理類來創建代理對象的方式生成一個動態代理對象

// 創建一個InvocationHandler對象
InvocationHandler handler = new MyInvocationHandler(...);
// 使用Proxy生成一個動態代理類proxyClass
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[]{Foo.class});
// 獲取proxyClass類中帶一個InvocationHandler參數的構造器
Constructor ctor = proxyClass.getConstructor(new Class[]{InvocationHandler.class});
// 調用ctor的newInstance方法來創建動態實例
Foo f = (Foo)ctor.newInstance(new Object[]{handler});

簡化代碼

// 創建一個InvocationHandler對象
InvocationHandler handler = new MyInvocationHandler(...);
// 使用Proxy直接生成一個動態代理對象
Foo f = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[]{Foo.class}, handler);

使用Proxy和InvocationHandler生成動態代理對象

import java.lang.reflect.*;

interface Person
{
	void walk();
	void sayHello(String name);
}
class MyInvocationHandler implements InvocationHandler
{
	/*
	執行動態代理對象的所有方法時,都會被替換成執行如下的invoke方法
	其中:
	proxy:代表動態代理對象
	method:代表正在執行的方法
	args:代表調用目標方法時傳入的實參。
	*/
	public Object invoke(Object proxy, Method method, Object[] args)
	{
		System.out.println("----正在執行的方法:" + method);
		if (args != null)
		{
			System.out.println("下面是執行該方法時傳入的實參爲:");
			for (var val : args)
			{
				System.out.println(val);
			}
		}
		else
		{
			System.out.println("調用該方法沒有實參!");
		}
		return null;
	}
}
public class ProxyTest
{
	public static void main(String[] args)
		throws Exception
	{
		// 創建一個InvocationHandler對象
		InvocationHandler handler = new MyInvocationHandler();
		// 使用指定的InvocationHandler來生成一個動態代理對象
		var p = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
			new Class[] {Person.class}, handler);
		// 調用動態代理對象的walk()和sayHello()方法
		p.walk();
		p.sayHello("孫悟空");
	}
}

動態代理和AOP

  • 開發實際應用的軟件系統時,通常會存在相同的代碼段重複出現的情況,在這種情況下,往往又會出現的做法是:選中那些代碼,一路複製黏貼,立即實現了系統功能,如果僅從實現功能角度來說,確實已經完成了軟件開發
  • 再有個升級版本就是通過調用相同的代碼實現代碼複用,如果需要修改則只需要修改一處的代碼多處生效,降低軟件後期維護的複雜度

如圖所示兩種方式:
在這裏插入圖片描述
但實際上第二個方式看似是解耦了,也的確有了分離的效果,但他也產生了另一個問題,代碼段1,2,3和被複用的代碼段分離開了,但代碼段1,2,3有何一個特定的方法耦合了,還未達到最理想的效果,最理想的效果是代碼段1,2,3既可以執行被複用的代碼段,又無需在程序中以硬編碼的方式直接調用被複用的代碼段,代理就可以做到這個效果

// 定義一個Dog接口
public interface Dog
{
	// info方法聲明
	void info();
	// run方法聲明
	void run();
}
// 爲Dog接口提供一個或多個實現類
public class GunDog implements Dog
{
	// 實現info()方法,僅僅打印一個字符串
	public void info()
	{
		System.out.println("我是一隻獵狗");
	}
	// 實現run()方法,僅僅打印一個字符串
	public void run()
	{
		System.out.println("我奔跑迅速");
	}
}
// 定義兩個通用方法用於被複用
public class DogUtil
{
	// 第一個攔截器方法
	public void method1()
	{
		System.out.println("=====模擬第一個通用方法=====");
	}
	// 第二個攔截器方法
	public void method2()
	{
		System.out.println("=====模擬通用方法二=====");
	}
}

藉助Proxy和InvocationHandler實現當程序調用info()和run()時,系統可以自動將method1()和method2()兩個通用方法插入info()和run()中執行

import java.lang.reflect.*;

public class MyInvocationHandler implements InvocationHandler
{
	// 需要被代理的對象
	private Object target;
	public void setTarget(Object target)
	{
		this.target = target;
	}
	// 執行動態代理對象的所有方法時,都會被替換成執行如下的invoke方法
	public Object invoke(Object proxy, Method method, Object[] args)
		throws Exception
	{
		var du = new DogUtil();
		// 執行DogUtil對象中的method1。
		du.method1();
		// 以target作爲主調來執行method方法
		Object result = method.invoke(target, args);
		// 執行DogUtil對象中的method2。
		du.method2();
		return result;
	}
}

invoke()方法包含了一行關鍵代碼Object result = method.invoke(target, args);,這行代碼通過反射儀target作爲主調來執行method方法,這就是回調了target對象的原有方法

import java.lang.reflect.*;

public class MyProxyFactory
{
	// 爲指定target生成動態代理對象
	public static Object getProxy(Object target) throws Exception
	{
		// 創建一個MyInvocationHandler對象
		MyInvocationHandler handler = new MyInvocationHandler();
		// 爲MyInvocationHandler設置target對象
		handler.setTarget(target);
		// 創建、並返回一個動態代理
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
	}
}

getProxy()方法爲target對象生成一個動態代理對象,這個動態代理對象與target實現了相同的接口,所以具有相同的public方法,因此從這個角度看,動態代理對象可以當成target對象使用,當程序調用動態代理對象的指定方法時,實際上將變爲執行MyInvocationHandler對象的invoke()方法
不難看出,當使用動態代理對象來代替target對象時,代理對象的方法就實現了前面的要求,程序執行info()、run()方法是既能插入method1()、method2()通用方法,但GunDog的方法中又沒有以硬編碼的方式調用method1()和method2()

public class Test
{
	public static void main(String[] args)
		throws Exception
	{
		// 創建一個原始的GunDog對象,作爲target
		Dog target = new GunDog();
		// 以指定的target來創建動態代理
		var dog = (Dog) MyProxyFactory.getProxy(target);
		dog.info();
		dog.run();
	}
}

執行這段代碼的結果爲:

D:\CrazyJava\CrazyJava\codes\18\18.5\DynaProxy>java Test
=====模擬第一個通用方法=====
我是一隻獵狗
=====模擬通用方法二=====
=====模擬第一個通用方法=====
我奔跑迅速
=====模擬通用方法二=====

程序中的dog對象實際上是動態代理對象,只是該動態代理對象也實現了Dog接口,所以也可以當成Dog對象使用,程序執行dog的info()和run()方法時,實際上會先執行method1()方法,再執行target對象的info()和run()方法,最後執行method2()

動態代理在AOP(Aspect Orient Programming面向切面編程)中被稱爲AOP代理,AOP代理可替代目標對象,AOP代理包含了目標對象的全部方法,但AOP代理中的方法與目標對象的方法存在差異:就是AOP代理裏的方法可以在執行目標方法之前之後插入一些通用處理
在這裏插入圖片描述

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