在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代理里的方法可以在执行目标方法之前之后插入一些通用处理