类加载机制与反射5——使用反射生成JDK动态代理

        在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。

  • 使用Proxy和InvocationHandler创建动态代理
  • 动态代理和AOP

1.使用Proxy和InvocationHandler创建动态代理

        Proxy提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类;
如果需要为一个或多个接口动态地创建实例,也可以使用Proxy来创建动态代理实例。
Proxy提供了如下两个方法来创建动态代理类和动态代理实例。
static Class<?> getProxyClass(ClassLoader loader,Class<?>...interfaces):创建一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口。第一个ClassLoader参数指定生成动态代理类的类加载器。
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interfaces制定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。

 实际上,即使采用第一个方法生成动态代理类之后,如果程序需要通过该代理类来创建对象,依然需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理对象都有一个与之关联的InvocationHandler对象。

            程序中可以采用先生成一个动态代理类,然后通过动态代理类来创建代理对象的方式生成一个动态代理对象:


下面程序示范了使用Proxy和InvocationHandler来生成动态代理对象:

interface Person
{
	void walk();
	void sayHello(String name);
}
class MyInvokationHandler 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 (Object 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 MyInvokationHandler();
		// 使用指定的InvocationHandler来生成一个动态代理对象
		Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
		// 调用动态代理对象的walk()和sayHello()方法
		p.walk();
		p.sayHello("孙悟空");
	}
}

实际上,在普通编程过程中,确实无须使用动态代理,但在编写框架或底层基础代码时,动态代理的作用就非常大。

2.动态代理和AOP

        下面介绍一种更实用的动态代理机制。
 开发实际应用的软件系统时,通常会存在相同代码段重复出现的情况,在这种情况下,对于许多刚开始从事软件开发的人而言,他们的做法是:选中那些代码,一路"复制"、"粘贴",立即实现了系统的功能,如果仅仅从软件功能上来看,他们确实已经完成了软件开发。

通过这种“复制”、“粘贴”方式开发出来的软件如图1所示。


        采用上述图片的方式实现具体的软件系统,在软件开发期间可能会觉得无所谓,但如果有一天需要修改程序的深色代码的实现,则意味着打开三分源代码进行修改。如果有100个地方甚至1000个地方使用了这段深色代码段,那么修改、维护这段代码的动作量将变成噩梦。

        在这种情况下,大部分稍有经验的开发者都会讲这段深色代码段定义成一个方法,然后让另外三段代码段直接调用该方法即可。在这种方式下,软件系统的结构如图2所示。


        采用上图所示的软件系统,如果需要修改深色部分的代码,则只要修改一个地方即可,而调用该方法的代码段,不管有多少个地方调用个该方法,都完全无须任何修改,只要被调用方法别修改了,所有调用该方法的地方就会自然改变——通过这种方式,大大降低了软件后期维护的复杂度。

        但采用这种方式实现代码复用依然产生了一个重要的问题:代码段1、代码段2、代码段3和深色diamante段分离开了,但代码段1、代码段2和代码段3又和一个特定方式耦合了。最理想的效果是:代码块1、代码块2和代码块3既可以执行深色代码部分,又无须在程序中以硬编码方式直接调用深色代码的方法,这是就可以通过动态代理来达到这种效果。

public interface Dog
{
	// info方法声明
	void info();
	// run方法声明
	void run();
}
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("=====模拟通用方法二=====");
	}
}
public class MyInvokationHandler 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
	{
		DogUtil du = new DogUtil();
		// 执行DogUtil对象中的method1。
		du.method1();
		// 以target作为主调来执行method方法
		Object result = method.invoke(target , args);
		// 执行DogUtil对象中的method2。
		du.method2();
		return result;
	}
}
public class MyProxyFactory
{
	// 为指定target生成动态代理对象
	public static Object getProxy(Object target)throws Exception
		{
		// 创建一个MyInvokationHandler对象
		MyInvokationHandler handler =
		new MyInvokationHandler();
		// 为MyInvokationHandler设置target对象
		handler.setTarget(target);
		// 创建、并返回一个动态代理
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , handler);
	}
}
public class Test
{
	public static void main(String[] args)throws Exception
	{
		// 创建一个原始的GunDog对象,作为target
		Dog target = new GunDog();
		// 以指定的target来创建动态代理
		Dog dog = (Dog)MyProxyFactory.getProxy(target);
		dog.info();
		dog.run();
	}
}
        通过上面的例子可以非常灵活地实现解耦。通常而言,使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的实际意义。通常都是为指定的目标对象生成动态代理。
这种动态代理在AOP(Aspect Orient Programming,面向切面编程)中被称为AOP代理,AOP代理可替代目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。

AOP代理包含的方法与目标对象包含的方法示意图如图3所示:





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值