JDK的动态代理机制

                                                                                                                           JDK Proxy OverView

jdk的动态代理是基于接口的,必须实现了某一个或多个随意接口才干够被代理,并且仅仅有这些接口中的方法会被代理。看了一下jdk带的动态代理api。发现没有样例实在是非常easy走弯路,所以这里写一个加法器的简单演示样例。

// Adder.java package test; public interface Adder { int add(int a, int b); } // AdderImpl.java package test; public class AdderImpl implements Adder { @Override public int add(int a, int b) { return a + b; } } 如今我们有一个接口Adder以及一个实现了这个接口的类AdderImpl,写一个Test測试一下。

// Test.java package test; public class Test { public static void main(String[] args) throws Exception { Adder calc = new AdderImpl(); int result = calc.add(1, 2); System.out.println("The result is " + result); } } 非常显然,控制台会输出: The result is 3 然而如今我们须要在加法器使用之后记录一些信息以便測试,但AdderImpl的源码不能更改,就像这样: Proxy: invoke add() at 2009-12-16 17:18:06 The result is 3 动态代理能够非常轻易地解决问题。

我们仅仅须要写一个自己定义的调用处理器(实现接口java.lang.reflect.InvokationHandler)。然后使用类java.lang.reflect.Proxy中的静态方法生成Adder的代理类。并把这个代理类当做原先的Adder使用就能够。 第一步:实现InvokationHandler。定义调用方法时应该运行的动作。

自己定义一个类MyHandler实现接口java.lang.reflect.InvokationHandler,须要重写的方法仅仅有一个: // AdderHandler.java package test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; class AdderHandler implements InvocationHandler { /** * @param proxy 接下来Proxy要为你生成的代理类的实例,注意,并非我们new出来的AdderImpl * @param method 调用的方法的Method实例。假设调用了add(),那么就是add()的Method实例 * @param args 调用方法时传入的參数。

假设调用了add()。那么就是传入add()的參数 * @return 使用代理后将作为调用方法后的返回值。假设调用了add()。那么就是调用add()后的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // ... } } 使用代理后,这种方法将代替指定的全部接口中的全部方法的运行。在本例中。调用adder.add()方法时,实际运行的将是invoke()。所以为了有正确的结果,我们须要在invoke()方法中手动调用add()方法。

再看看invoke()方法的參数,正好符合反射须要的全部条件。所以这时我们立即会想到这样做: Object returnValue = method.invoke(proxy, args); 假设你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例。实际上就是使用代理之后adder引用所指向的对象。

由于我们调用了adder.add(1, 2),才使得invoke()运行,假设在invoke()中使用method.invoke(proxy, args)。那么又会使invoke()运行。没错。这是个死循环。

然而。invoke()方法没有别的參数让我们使用了。最简单的解决方法就是,为MyHandler增加一个属性指向实际被代理的对象。所以,由于jdk的冷幽默,我们须要在自己定义的Handler中增加下面这么一段: // 被代理的对象 private Object target; public AdderHandler(Object target) { this.target = target; } 喜欢的话还能够加上getter/setter。接着,invoke()就能够这么用了: // AdderHandler.java package test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Date; class AdderHandler implements InvocationHandler { // 被代理的对象 private Object target; public AdderHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 调用被代理对象的方法并得到返回值 Object returnValue = method.invoke(target, args); // 调用方法前后都能够增加一些其它的逻辑 System.out.println("Proxy: invoke " + method.getName() + "() at " + new Date().toLocaleString()); // 能够返回不论什么想要返回的值 return returnValue; } } 第二步:使用jdk提供的java.lang.reflect.Proxy生成代理对象。 使用newProxyInstance()方法就能够生成一个代理对象。把这种方法的签名拿出来: /** * @param loader 类载入器。用于载入生成的代理类。 * @param interfaces 须要代理的接口。这些接口的全部方法都会被代理。 * @param h 第一步中我们建立的Handler类的实例。 * @return 代理对象,实现了全部要代理的接口。 */ public static Object newProxyInstance(ClassLoader loader, Class<?

>[] interfaces, InvocationHandler h) throws IllegalArgumentException 这种方法会做这样一件事情,他将把你要代理的全部接口用一个由代码动态生成的类类实现。全部的接口中的方法都重写为调用InvocationHandler.invoke()方法。这个类的代码相似于这样: // 模拟Proxy生成的代理类,这个类是动态生成的,并没有相应的.java文件。 class AdderProxy extends Proxy implements Adder { protected AdderProxy(InvocationHandler h) { super(h); } @Override public int add(int a, int b) { try { Method m = Adder.class.getMethod("add", new Class[] {int.class, int.class}); Object[] args = {a, b}; return (Integer) h.invoke(this, m, args); } catch (Throwable e) { throw new RuntimeException(e); } } } 据api说,全部生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,并且Proxy里面也是调用sun.XXX包的api生成;普通情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类载入并实例化一个对象作为代理返回。 看明确这种方法后,我们来改造一下main()方法。 // Test.java package test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) throws Exception { Adder calc = new AdderImpl(); // 类载入器 ClassLoader loader = Test.class.getClassLoader(); // 须要代理的接口 Class[] interfaces = {Adder.class}; // 方法调用处理器,保存实际的AdderImpl的引用 InvocationHandler h = new AdderHandler(calc); // 为calc加上代理 calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h); /* 什么?你说还有别的需求? */ // 还有一个处理器,保存前处理器的引用 // InvocationHandler h2 = new XXOOHandler(h); // 再加代理 // calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h2); int result = calc.add(1, 2); System.out.println("The result is " + result); } } 输出结果会是什么呢? Proxy: invoke add() at 2009-12-16 18:21:33 The result is 3 对照一下之前的结果,你会发现这点东西写了我一个多小时。再来看看JDK有多懒: target全然能够在代理类中生成。 实际方法都须要手动调用,可见代理类中重写全部的方法都仅仅有一句话:return xxx.invoke(ooo); 只是这么写也有他的理由,target自己管理,方法你爱调不调 ﹃_﹃;假设他调了。InvocationHandler接口中恐怕就须要两个方法了,还要推断返回、处理參数等等。 原文地址:http://www.cnblogs.com/huxi/archive/2009/12/16/1625899.html。




原文地址:https://www.cnblogs.com/yangykaifa/p/6745328.html