飞道的博客

JDK动态代理简单使用方法+通过Proxy和InvocationHandler源码解析原理

349人阅读  评论(0)

一、JDK动态代理的使用

JDK动态代理模式是基于接口,所以我们先定义一个Moveable接口:

public interface Moveable {
    void go();
}

再定义一个需要被代理的Car类:

public class Car implements Moveable {
    @Override
    public void go() {
        System.out.println("car is moving wu wu wu....");
    }
}

现在我们遇到的问题或者需求,要给Car类的对象在执行go()方法时记录执行时间,Car类的对象是不能够计算自己本身运行的时间,实现这个需求我们也可以通过继承子类来实现,例如:

public class TimeCar implements Moveable {
    @Override
    public void go() {
         ////before calc time
        System.out.println("car is moving wu wu wu....");
         ////after calc time
    }
}

但是继承这种方式不建议使用,如果需求频繁变更(要求记录日志、权限),就会造成子类过多并且可重用性不高。优先使用组合的方式,所以我们可以通过组合的方式来实现。思想就是在执行Car类对象的go()方法前后添加计算时间这个逻辑,通过一个代理对象来实现,JDK的动态代理对象需要实现InvocationHandler接口,我们添加一个计算时间的代理类:

public class CarTimeInvocationHandler implements InvocationHandler {
    /**被代理对象*/
    private Moveable moveable;

    public CarTimeInvocationHandler(Moveable moveable) {
        this.moveable = moveable;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ///before process
        long start = System.currentTimeMillis();
        Object o = method.invoke(moveable, args);
        try {
            Thread.sleep(new Random().nextInt(10_000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ///after process
        long end = System.currentTimeMillis();
        System.out.println("static proxy car run time:" + (end - start));
        return o;
    }
}

DynamicMain Main类测试我们的代理是否成功。

public class DynamicMain {
    public static void main(String[] args) {
        Car car = new Car();
        ///保存生成的代理类
        //        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        Moveable carTime = (Moveable) Proxy.newProxyInstance(car.getClass().getClassLoader(), new Class[]{Moveable.class}, new CarTimeInvocationHandler(car));
        System.out.println("=================Time=================");
        carTime.go();
    }
}

运行结果如下,通过结果我们可以确定时间被成功计算出来了。

=================Time=================
car is moving wu wu wu....
static proxy car run time:2874

二、原理说明

首先我们查看InvocationHandler接口的源码,接口只定义了一个invoke方法。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

再查看Proxy类的源代码,截取关键部分展示如下:

public class Proxy implements java.io.Serializable {

    private static final long serialVersionUID = -2222568056686623797L;

    /** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };
    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
          ///PS:获取`JDK`生成的代理类对象
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
             ///PS:生成代理类的一对象,注入InvocationHandler对象
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
.....
}

注意其中两行就可以了,Class<?> cl = getProxyClass0(loader, intfs);获取JDK生成的代理类对象,return cons.newInstance(new Object[]{h});生成代理类的一对象,并且将传入的InvocationHandler对象注入给代理类生成的对象,到这里我们就能大概知道JDK动态代理究竟是怎么实现的了。

梳理一下流程:

  • 第一步通过public Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法返回一个由JDK生成的实现了所有Class<?>[] interfaces接口的代理类的一个代理对象;

  • 第二步生成的代理对象执行第一步传入的接口(Moveable)中的方法(go()),通过查看JDK生成的代理类,可以发现代理类维持了一个InvocationHandler的对象,就是第一步传入的InvocationHandler的对象,接口(Moveable)中的方法(go())方法内部会执行第一步传入的InvocationHandler的对象的invoke(...)方法,从而实现前置后置操作;

通过在main方法第一行设置属性System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");可以保存内存中生成的代理类到文件,生成的代理类如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import cn.missbe.model.factory.Moveable;
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 Moveable {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        /**
        Proxy的newProxyInstance方法返回对象语句
        return cons.newInstance(new Object[]{h});会生成当前代理类的对象,
        里面的h对应的就是代理类构造方法的var1
        InvocationHandler对象会被代理类维持,在调用接口方法时实际调用的是InvocationHandler 对象的invoke方法
        */
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            /**h就是传入到InvocationHandler 对象,调用InvocationHandler对象的invoke方法*/
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } 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);
        }
    }

    public final void go() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("cn.missbe.model.factory.Moveable").getMethod("go");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

通过实例和源码查看,我们大概能理解JDK动态代理的原理,JDK生成代理类的方式好像是通过asm,怎么生成代理类的可以深入源码现了解,整个时序图如下图:

通过时序图可以清楚的看到整个流程,Proxy调用newProxyInstance方法创建代理类实例时,会通过asm的方式(还有其它方式也可以实现)动态生成一个代理类,些代理类会维持创建对象注入的InvocationHandler对象,InvocationHandler对象就是需要自己实现的前后处理。当生成的代理对象调用接口的方法时,实际上执行的是生成的$Proxy0维持的InvocationHandler对象hinvoke()方法,所以具体的代理逻辑可以写到InvocationHandler接口的具体实现类中去,可能写得有点乱,不清楚可以留言提问哈。

欢迎关注南阁公众号


转载:https://blog.csdn.net/lovequanquqn/article/details/105818091
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场