在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代理裏的方法可以在執行目標方法之前之後插入一些通用處理