Java使用反射生成JDK动态代理
1.* 使用反射生成JDK动态代理
在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。
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对象。
提示:计算机是很“蠢”的,当程序使用反射方式为指定接口生成系列动态代理对象时,这些动态代理对象的实现类实现了一个或多个接口。动态代理对象就需要实现一个或多个接口里定义的所有方法,但问题是:系统怎么知道如何实现这些方法?这个时候就轮到InvocationHandler对象登场了,当执行动态代理对象里的方法时,实际上会替换成调用InvocationHandler对象的invoke方法。
程序中可以采用先生成一个动态代理类,然后通过动态代理类来创建代理对象的方式生成一个动态代理对象。代码片段如下:
点击查看代码
//创建一个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来生成动态代理对象:
上面程序首先提供了一个Person接口,该接口中包含了walk()和sayHello()两个抽象方法,接着定义了一个简单的InvocationHandler实现类,定义该实现类时需要重写invoke()方法,调用代理对象的所有方法时都会被替换成调用该invoke()方法。该invoke()方法中的三个参数解释如下:
- proxy:代表动态代理对象
- method:代表正在执行的方法
- args:代表调用目标方法时传入的实参
上面程序中先是创建了一个InvocationHandler对象,然后根据InvocationHandler对象创建了一个动态代理对象。运行上面程序,会看到如图所示的运行效果:
从图可以看出,不管程序是执行代理对象的walk()方法,还是执行代理对象的sayHello()方法,实际上都是执行InvocationHandler对象的invoke()方法。
看完了上面的示例程序,可能会觉得这个程序没有太大的实用价值,难以理解Java动态代理的魅力。实际上,在普通编程过程中,确实无须使用动态代理,但在编写框架或底层基础代码时,动态代理的作用就非常大。
1.*.& 动态代理和AOP
根据前面介绍的Proxy和InvocationHandler,实在很难看出这种动态代理的优势。下面介绍一种更实用的动态代理机制。
只要我们开发一个实际使用的软件系统,就总会存在相同代码段重复出现的情况,在这种情况下,对于许多刚开始从事软件开发的人而言,他们的做法是:选中那些代码,一路“复制“、“粘贴”,立即实现了系统功能,如果仅仅从软件功能上来看,他们确实已经完成了软件开发。
通过这种“复制”、“粘贴”方式开发出来的软件如图所示:
采用图示结构实现的软件系统,在软件开发期间可能会觉得无所谓,但如果有一天需要修改程序的深色代码的实现,则意味着打开3份源代码进行修改。如果有100个地方甚至1000个地方使用了这段深色代码段,那么修改、维护这段代码的工作量将变成噩梦。
在这种情况下,大部分稍有经验的开发者都会将这段深色代码段定义成一个方法,然后让另外三段代码段直接调用该方法即可。在这种方式下,软件系统的结构如图:
对于如图所示的软件系统,如果需要修改深色部分的代码,则只要修改一个地方即可,而调用该方法的代码段,不管有多少个地方调用了该方法,都完全无须任何修改,只要被调用方法被修改了,所有调用该方法的地方就会自然改变,通过这种方式,大大降低了软件后期维护的复杂度。
但采用这种方式来实现代码复用依然产生一个重要问题:代码段1、代码段2、代码段3和深色代码段分离开了,但代码段1、代码段2和代码段3又和一个特定方法耦合了!最理想的效果是:代码块1、代码块2和代码块3既可以执行深色代码部分,又无须在程序中以硬编码方式直接调用深色代码的方法,这时就可以通过动态代理来达到这种效果。
由于JDK动态代理只能为接口创建动态代理,所以下面先提供一个Dog接口,该接口代码非常简单,仅仅在该接口里定义了两个方法:
点击查看代码
public interface Dog {
//info方法声明
void info();
//run方法声明
void run();
}
上面接口里只是简单地定义了两个方法,并未提供方法实现。如果我们直接使用Proxy为该接口创建动态代理对象,则动态代理对象的所有方法的执行效果又将完全一样。在这种情况下,我们先为该Dog接口提供一个简单的实现类GunDog,代码如下:
点击查看代码
public class GunDog implements Dog {
//info方法实现,仅仅打印一个字符串
public void info() {
System.out.println("我是一只猎狗");
}
//run方法实现,仅仅打印一个字符串
public void run() {
System.out.println("我奔跑迅速");
}
}
上面代码没有丝毫的特别之处,该Dog的实现类仅仅为每个方法提供了一个简单实现。回到开始我们需要实现的功能:让代码段1、代码段2和代码段3既可以执行深色代码部分,又无须在程序中以硬编码方式直接调用深色代码的方法。此时我们假设info()、run()两个方法代表代码段1、代码段2,那么要求:程序执行info()、run()方法时能调用某个通用方法,但又不想以硬编码方式调用该方法。
下面提供一个DogUtil类,该类里包含两个通用方法:
点击查看代码
public class DogUtil {
//第一个拦截器方法
public void method1() {
System.out.println("=====模拟第一个通用方法=====");
}
//第二个拦截器方法
public void method2() {
System.out.println("=====模拟通用方法二=====");
}
}
借助于Proxy和InvocationHandler就可以实现,当程序调用info()方法和run()方法时,系统可以“自动”将method1()和method2()两个通用方法插入info()和run()方法中执行。
这个程序的关键在于下面的MyInvokationHandler类,该类是一个InvocationHandler实现类,该实现类的invoke()方法将会作为代理对象的方法实现:
点击查看代码
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;
}
}
上面程序实现invoke()方法时包含了一行关键代码,这行代码通过反射以target作为主调来执行method方法,这就是回调了target对象的原有方法。在该关键代码之前调用DogUtil对象的method1()方法,在该关键代码之后调用DogUtil对象的method2()方法。
下面再为程序提供一个MyProxyFactory类,该对象专为指定的target生成动态代理实例:
点击查看代码
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);
}
}
上面的动态代理工厂类提供了一个getProxy()方法,该方法为target对象生成一个动态代理对象,这个动态代理对象与target实现了相同的接口,所以具有相同的public方法,从这个意义上来看,动态代理对象可以当成target对象使用。当程序调用动态代理对象的指定方法时,实际上将变为执行MyInvokationHandler对象的invoke()方法。例如,调用动态代理对象的info()方法,程序将开始执行invoke()方法,其执行步骤如下:
- 创建DogUtil实例。
- 执行DogUtil实例的method1()方法。
- 使用反射以target作为调用者执行info()方法。
- 执行DogUtil实例的method2()方法。
看到上面的执行过程,可以发现:当使用动态代理对象来代替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来创建动态代理对象
Dog dog = (Dog) MyProxyFactory.getProxy(target);
dog.info();
dog.run();
}
}
上面程序中的dog对象实际上是动态代理对象,只是该动态代理对象也实现了Dog接口,所以也可以当成Dog对象使用。程序执行dog的info()和run()方法时,实际上会先执行DogUtil的method1()方法,再执行target对象的info()和run()方法,最后执行DogUtil的method2()方法。运行上面程序,会看到如图所示的运行结果:
通过图示的运行结果来看,不难发现采用动态代理可以非常灵活地实现解耦。通常而言,当我们使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的实际意义。通常都是为指定的目标对象生成动态代理。
这种动态代理在AOP(Aspect Orient Programming,面向切面编程)中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。
AOP代理包含的方法与目标对象包含的方法示意图如图所示: