JDK动态代理

来源:互联网 发布:fps软件下载 编辑:程序博客网 时间:2024/05/29 03:11

背景

JDK动态代理是java为我们提供的很好用的代理工具。JDK动态代理只能为我们提供面向接口的代理,但并不是所有需要代理的类都符合这样的要求。一个解决的方法是使用另外一个比较常用的工具cglib。spring在实现代理的时候就是这样当需要为接口代理时使用JDK动态代理,为类代理时使用cglib,这里是出于性能的权衡。当然还有其他级别的代理,这里不多涉及了。如无特殊说明,本文使用的JDK源码版本均为1.8.0_71。本文的主要内容如下:

  • JDK动态代理的基本使用
  • JDK动态代理原理的解析
  • JDK代理和cglib的比较

使用JDK动态代理

我们知道代理类在软件设计的职责分配方面起到重要的作用。使用代理可以将不同维度的实现分开来,比如让代理类去做一些日志类或记录时间之类的工作,那么被代理的类就可以集中精力处理自己的逻辑。这样做还有一个好处就是使程序更加简洁减少编程工作量而不至于为每个类都编写代理。
定义一个接口Hello

public interface Hello {    void say(String name);}

定义一个实现类:

public class Helloplm implements Hello {    @Override    public void say(String name) {        System.out.println("hello "+name        );    }}

如何为Hello这个接口实现一个代理类使得在say()方法的调用前后分别输出内容呢?
我们先来看一个糟糕的版本:

public class HelloProxy implements Hello {    private Hello hello;    HelloProxy(){        hello = new Helloplm();    }    private void after(){        System.out.println("after");    }    private void before(){        System.out.println("before");    }    @Override    public void say(String name) {        before();        hello.say(name);        after();    }}

测试类:

public class TestStaticProxy {    public static void main(String[] args) {        HelloProxy proxy = new HelloProxy();        proxy.say(" static proxy");    }}

这种方式可以实现,但显然相当耦合以及低级,我们需要为每一个实现类都写一个代理类,这样程序将会到处都充满XXXProxy这样的类。
我们来用JDK提供的工具来实现动态代理。仍然使用Hello接口和Helloplm实现类,代理类则如下:

public class DynamicProxy implements InvocationHandler {    private Object target;    public  DynamicProxy(Object target){        this.target = target;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        before();        Object result = method.invoke(target,args);        after();        return result;    }    private void before(){        System.out.println("before");    }    private void after(){        System.out.println("after");    }    @SuppressWarnings("unchecked")    public <T> T getProxy(){        return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),                target.getClass().getInterfaces(),this);    }}

测试类:

public class TestJDKProxy {    public static void main(String[] args) {        Hello hello = new Helloplm();        DynamicProxy proxy = new DynamicProxy(hello);        Hello helloProxy = proxy.getProxy();        helloProxy.say(" JDK Dynamic");    }}

运行结果如下:

这里写图片描述

这个过程是如何实现代理功能的呢?

JDK动态代理原理探究

invocationHandler

invocationHandler是java.lang.reflect包中的一个接口,这个接口只有一个方法:

public Object invoke(Object proxy, Method method, Object[] args)        throws Throwable;

Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.

就是说当动态生成的代理类(本例中是$Proxy0,后面会介绍)中的方法(say())被调用时,这个方法将会被调用。那么动态生成的代理类是什么?它又是怎么生成的?

代理类的生成

java.lang.reflect.Proxy有一个静态方法用来获取代理类。

@CallerSensitive    public static Object newProxyInstance(ClassLoader loader,  Class<?>[] interfaces, InvocationHandler h)        throws IllegalArgumentException    {       //篇幅原因省略次要代码        /*         * Look up or generate the designated proxy class.         */         //此处通过传入需要被代理的类的类加载器和接口参数,用getProxyClass0来获取代理类,其实现是使用了weakCache,下面看getProxy0这个方法        Class<?> cl = getProxyClass0(loader, intfs);       //省略获得代理类后为其构造的代码    }

获取代理类的方法:getProxyClass0

private static Class<?> getProxyClass0(ClassLoader loader,  Class<?>... interfaces) {        if (interfaces.length > 65535) {            throw new IllegalArgumentException("interface limit exceeded");        }        // If the proxy class defined by the given loader implementing        // the given interfaces exists, this will simply return the cached copy;        // otherwise, it will create the proxy class via the ProxyClassFactory        //这一句来返回代理类        return proxyClassCache.get(loader, interfaces);    }

proxyClassCache是WeakCache的一个实例。

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

可以把WeakCache想象成一个Map,里面的key是被代理类的类加载器和接口等信息,而值就是我们需要的代理类。而这个“Map”的get方法会根据key返回被代理类,如果不存在缓存也就是被代理类没有被创建则创建代理类。具体细节这里不再展开。也就是说,代理类是ProxyClassFactory产生的,我们来看一下这个类:
这个类是Proxy类的一个内部类

/**     * A factory function that generates, defines and returns the proxy class given     * the ClassLoader and array of interfaces.     */    private static final class ProxyClassFactory        implements BiFunction<ClassLoader, Class<?>[], Class<?>>    {        //出于篇幅考虑省略接口验证和名字拼接代码            /*             * Generate the specified proxy class.             */            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                proxyName, interfaces, accessFlags);            try {                return defineClass0(loader, proxyName,                                    proxyClassFile, 0, proxyClassFile.length);            } catch (ClassFormatError e) {                /*                 * A ClassFormatError here means that (barring bugs in the                 * proxy class generation code) there was some other                 * invalid aspect of the arguments supplied to the proxy                 * class creation (such as virtual machine limitations                 * exceeded).                 */                throw new IllegalArgumentException(e.toString());            }        }    }

generateProxyClass这个方法来生成代理类,这个方法在ProxyGenerator类中定义,这个类是sun.misc包中的类。然后通过defineClass0这个方法返回。defineClass0是个native方法!!!好吧,再班门弄斧下去就有点不懂装懂了。

好的我们来梳理一下这个过程:
首先我们在测试类中通过Proxy.newInstance()来获得一个动态生成的类,这个方法只是起封装作用,真正获得动态生成类的是getClass0方法,getClass0方法有一个WeakCache的引用,通过调用WeakCache的get方法来获得类,如果已在缓存里面则返回类,如果没有则创建它。创建的过程是通过ProxyClassFactory这个类中调用ProxyGenerator.generateProxyClass方法来创建的。至于具体怎么创建本文不再展开(其实是不懂了)。

顺序图大概是这样:
这里写图片描述

然后我们就是通过调用这个动态生成的类的say方法,去调用DynamicProxy的invoke方法,在DynamicProxy的invoke方法中通过反射去调用我们要为之代理的Helloplm类的say方法,从而实现代理功能。

动态生成的$Proxy0.class

sun.misc.ProxyGenerator类中有一个变量saveGeneratedFiles,默认为false,我们把它设置为true就可以看到生成的$Proxy0.class文件了

 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
public final class $Proxy0 extends Proxy implements Hello {    private static Method m1;    private static Method m2;    private static Method m3;    private static Method m0;    public $Proxy0(InvocationHandler var1) throws  {        super(var1);    }    public final boolean equals(Object var1) throws  {        try {            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();        } catch (RuntimeException | Error var3) {            throw var3;        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);        }    }    public final String toString() throws  {        try {            return (String)super.h.invoke(this, m2, (Object[])null);        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }//这个方法就是我们要调用的say方法    public final void say(String var1) throws  {        try {            super.h.invoke(this, m3, new Object[]{var1});        } catch (RuntimeException | Error var3) {            throw var3;        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);        }    }    public final int hashCode() throws  {        try {            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }    static {        try {            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);            m3 = Class.forName("demo.proxy.Hello").getMethod("say", new Class[]{Class.forName("java.lang.String")});            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);        } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());        } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());        }    }}

say方法中super.h.invoke(this, m3, new Object[]{var1});就是调用DynamicProxy中的invoke方法的。
至此应该算是把动态代理的过程和原理简单说了一下。下面来看cglib的代理。

cglib动态代理和Jdk动态代理的比较

通过前面两节我们发现,jdk动态代理只能代理接口,是的很遗憾。但是cglib这颗银弹给我们带来了很多功能。
我们来看一个例子。
下面我们要为HelloCGlib这个类实现代理功能:

public class HelloCGlib {    public void say(String name){        System.out.println("hello "+name);    }}

我们创建的代理类:

public class CGLibProxy implements MethodInterceptor {    private static CGLibProxy instance = new CGLibProxy();    public static CGLibProxy getInstance(){return instance;}    private CGLibProxy(){    }    public <T> T getProxy(Class<T> cls){        return (T) Enhancer.create(cls,this);    }    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {        before();        Object result = methodProxy.invokeSuper(o,objects);        after();        return result;    }    private void before(){        System.out.println("before");    }    private void after(){        System.out.println("after");    }}

测试类:

public class TestCGLibProxy {    public static void main(String[] args) {        HelloCGlib hello = CGLibProxy.getInstance().getProxy(HelloCGlib.class);        hello.say(" CGLib proxy");    }}

cglib这个开源项目的源代码我没有仔细去研究,不过鉴于很多框架都在使用cglib,它应该有很多强大的功能。这里cglib和jdk动态代理的比较只是简单指出了他们一个只能代理接口而另一个可以针对实现代理,以及给出了简单的使用代码,而没有更进一步的探讨。本文中的代码在我的github上:

代理的简单实现

完。

0 0
原创粉丝点击