JAVA 基于反射的动态代理

来源:互联网 发布:c语言疯狂讲义 pdf 编辑:程序博客网 时间:2024/05/21 07:14

设计模式中有一种模式叫做代理模式,主要用来处理当该类因为某些原因不方便访问,但又要调用访问他的方法时,这时可以使用代理模式.设计一个代理类,通过代理类访问真实类.

代理分为静态代理和动态代理.

静态代理是程序在编译期代理类就已经确定自己要代理什么对象了,比如,有一位歌星A(真实类),有一位经纪人a(代理类),这个经纪人身上贴了个标签:我只是A的经纪人,就管A的事,其他歌星的事别找我,找我也不管用.某大公司商演,上次找的经纪人a,这次想请歌星B来演出,于是,他们还要再去联系歌星B的经纪人b.

静态代理扩展起来是比较麻烦的,添加其他真实类就要添加相应代理类.

而动态代理就是有某位经纪人在演艺圈混的风生水起,同时担任多个人的经纪人,某大公司商演再想换人,都是找这一个经纪人,演艺圈又出了个很火的新人(新的真实类),这个经纪人再把他也代理了.扩展起来相对简单.

静态代理和动态代理的优势分别就是,静态代理容易理解及编写代码,但扩展麻烦.动态代理扩展简单,但不易理解.


原始的代理模式,为了透明访问,代理类和真实类要实现相同的接口,而JAVA中基于反射的动态代理也是这样.

一般我们使用基于反射的动态代理可以这样写:

public class ProxyHandler implements InvocationHandler{private Object target;/***** * 返回要代理的类 * @param target * @return */    public Object createProxy(Object target){    this.target = target;    //返回代理类实例    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);    }public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// TODO Auto-generated method stubmethod.invoke(target, args);return null;}}

ProxyHandler实现InvocationHandler接口

注意:ProxyHandler并不是我们需要的代理类,他只是个代理辅助类或称为调用处理类(里面有个重要的invoke方法),调用ProxyHandler的createProxy()方法获得的类才是真正的代理类.但这个真正代理类不是我们自己编写代码,而是使用反射技术返回的,他也利用反射技术实现了真实类的接口.

关于接口InvocationHandler,API文档中是这样说的:

  • Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to theinvoke method of its invocation handler.

用小渣渣英语翻译一下就是:每一个代理实例都有一个代理处理类,当代理实例的方法被调用时,代理处理类里的invoke方法被调用.

至于为什么代理处理类里的invoke会被调用就更有意思了:

首先,解释一下代理类生成代码:

return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

我们使用代理类,希望像使用真实类一样,所以代理类也应该实现真实类实现的接口,而同时,当我们调用代理类方法时,会回来调用代理处理类中的invoke()方法,这是为什么在new新的代理类时传这两个参数:target.getClass().getInterfaces()(表示真实类的所有接口), this(代理处理类的引用).

同时我们有理由猜测,我们生成的代理类中的方法实现会通过我们传进去的this引用(或代理处理类引用)来调用他自己的invoke方法.

接下来有意思的来了,我们读源码看代理类生成过程有这样一句:

 /*             * Generate the specified proxy class.             */            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                proxyName, interfaces, accessFlags);

上边一句代码表示根据传进来的参数生成了一份字节码.其实我们接着详细读下去会发现,系统将生成的字节码存储在了硬盘上,如下:

 Path path;                        if (i > 0) {                            Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));                            Files.createDirectories(dir);                            path = dir.resolve(name.substring(i+1, name.length()) + ".class");                        } else {                            path = Paths.get(name + ".class");                        }                        Files.write(path, classFile);


这不就和我们调用我们自己写的类一样了吗?不同的是代理类是系统帮我们生成的,我们用上面代码其实可以自己生成想要的代理类字节码,如下,生成了一份String的代理类字节码:

public class TestProxy {public static void main(String[] args) throws Exception {byte[] byteArray = ProxyGenerator.generateProxyClass("$Proxy0", String.class.getInterfaces());FileOutputStream fos = new FileOutputStream(new File("C:\\Users\\Administrator\\Desktop\\out.class"));fos.write(byteArray);fos.flush();fos.close();}}

桌面上生成了一个out.class文件,这不就是我们java文件编译后的字节码文件嘛!反编译一把,这样的:


import java.io.Serializable;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0  extends Proxy  implements Serializable, Comparable, CharSequence{  private static Method m5;  private static Method m3;  private static Method m1;  private static Method m4;  private static Method m0;  private static Method m6;  private static Method m2;    public $Proxy0(InvocationHandler paramInvocationHandler)    throws   {    super(paramInvocationHandler);  }    public final int length()    throws   {    try    {      return ((Integer)this.h.invoke(this, m5, null)).intValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }    public final int compareTo(Object paramObject)    throws   {    try    {      return ((Integer)this.h.invoke(this, m3, new Object[] { paramObject })).intValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }    public final boolean equals(Object paramObject)    throws   {    try    {      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }    public final char charAt(int paramInt)    throws   {    try    {      return ((Character)this.h.invoke(this, m4, new Object[] { Integer.valueOf(paramInt) })).charValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }    public final int hashCode()    throws   {    try    {      return ((Integer)this.h.invoke(this, m0, null)).intValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }    public final CharSequence subSequence(int paramInt1, int paramInt2)    throws   {    try    {      return (CharSequence)this.h.invoke(this, m6, new Object[] { Integer.valueOf(paramInt1), Integer.valueOf(paramInt2) });    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }    public final String toString()    throws   {    try    {      return (String)this.h.invoke(this, m2, null);    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }    static  {    try    {      m5 = Class.forName("java.lang.CharSequence").getMethod("length", new Class[0]);      m3 = Class.forName("java.lang.Comparable").getMethod("compareTo", new Class[] { Class.forName("java.lang.Object") });      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });      m4 = Class.forName("java.lang.CharSequence").getMethod("charAt", new Class[] { Integer.TYPE });      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);      m6 = Class.forName("java.lang.CharSequence").getMethod("subSequence", new Class[] { Integer.TYPE, Integer.TYPE });      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);      return;    }    catch (NoSuchMethodException localNoSuchMethodException)    {      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());    }    catch (ClassNotFoundException localClassNotFoundException)    {      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());    }  }}

一看就明白了,代理类在所有的方法里又通过传进来的代理处理类的引用调用了invoke方法.


总结整个过程:

通过Proxy静态方法获得代理类,强转一下代理类成高层次接口类型,因为代理类也实现了真实类的接口,可以调用和真实类相同的方法,但代理类中方法的实现却和真实类不同,代理类方法又调用了代理类处理类的invoke方法,invoke方法使用反射技术又调用了真实类的方法,从而实现代理的整个过程.


在invoke中随手打印了一下proxy没想到还引出了StackOverflowError,查了一下baidu,又看了一下out.class的源码发现问题应该是这样引起的:

打印proxy,就要调用proxy对象的toString方法,而proxy中的方法都回调了invoke方法,而就是在invoke方法中进行的打印操作,形成了递归...

这其中还有不少过程值得细细琢磨,以上只是个人粗浅见解,如有错误,欢迎指正!抓狂

end


原创粉丝点击