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

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/105984413