JDK动态代理探索

来源:互联网 发布:域名备案要钱吗 编辑:程序博客网 时间:2024/05/17 23:55

代理模式简析

代理模式的定义如下:

Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)

话不多说,上UML图:
静态代理UML

我们看一下类图中角色的定义:

  • Subject 抽象主题角色
    抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
  • RealSubject 具体主题角色
    也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。(后文我们称它为目标对象)
  • Proxy 代理主题角色
    也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

我们看一下代码清单:

  • 接口代码(抽象主题类)
/** * 抽象主题类 */public interface Subject {    /**     * 定义一个方法     */    public void request();}

在接口中我们定义了一个方法request来作为方法的代表,RealSubject对它进行实现。

  • 目标类(真实主题类)
/** * 真实主题类 */public class RealSubject implements Subject{    /**     * 实现方法     */    @Override    public void request() {        //业务逻辑处理        System.out.println("I will find you ,my girl.");    }}

RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上。

  • 代理类
/** * 代理类 */public class Proxy implements Subject{    //要代理哪个实现类    private Subject subject = null;    //默认被代理者    public Proxy(Subject subject) {        this.subject = subject;    }    @Override    public void request() {        this.preOption();        this.subject.request();        this.afterOption();    }    //预处理    private void preOption(){        System.out.println("Before find you ,I will make self good.");    }    //善后处理    private void afterOption(){        System.out.println("After find you ,I will make ourself good.");    }}

如下是场景类的调用

/** * 场景类 */public class Client {    public static void main(String[] args) {        //创建被代理类(目标类)        Subject target = new RealSubject();        //创建代理类,同时注入目标类        Proxy proxy = new Proxy(target);        proxy.request();    }}

控制台打印结果如下:

Before find you ,I will make self good.
I will find you ,my girl.
After find you ,I will make ourself good.

通过上面的UML类图以及代码的具体实现,我们可以看到:有一个RealSubject,这个对象是目标对象,而在代理模式的设计中,会设计一个接口和目标对象一致的代理对象Proxy,它们都实现了接口Subject的request方法。在这种情况下,对目标对象的request的调用,往往就被代理对象“浑水摸鱼”给拦截了,通过这种拦截,为目标对象的方法操作做了铺垫,所以称之为代理模式。

JDK动态代理

动态代理顾名思义,就是动态的创建代理对象。即在程序运行时,根据被代理类(目标类)及其实现的接口,动态的创建一个代理类。当调用代理类的实现的抽象方法时,就发起对被代理类(目标类)同样方法的调用。

在JDK的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface),另一个则是Proxy(Class)。它们都在java.lang.reflect包下。

1. InvocationHandler的描述信息如下:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

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 the invoke method of its invocation handler.

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用 一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

InvocationHandler这个接口有唯一一个方法 invoke:

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

这个方法接收三个参数和返回一个Object类型:

  • Object proxy: 被代理的对象即目标对象

  • Method method:目标对象的方法

  • Object[] args:调用目标对象某个方法时接受的参数,可以没有,也可以有多个或 null

  • 返回值Object,是目标对象方法的返回类型

InvocationHandler 接口中的方法就是执行被代理对象(目标对象)中的方法。

2.Proxy的描述

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Proxy 提供一些静态方法来创建动态代理类和实例。Proxy 主要方法就是 newProxyInstance 这个方法:

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

其中三个参数:

  • ClassLoader loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载。

  • Class<\?>[] interfaces:一个Interface对象的数组,表示的是我将要给我需要的代理对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了。

  • InvocationHandler h: 实现了InvocationHandler接口的实现类的对象。表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

  • 方法的返回结果: 一个代理对象的实例

在编码中,newProxyInstance方法的ClassLoader、interface参数我一般会传入目标类的ClassLoader、目标类的interface。

3.动态代理代码清单

在介绍完Proxy跟InvocationHandler后,下面是具体的代码清单:

  • 接口类
public interface Subject {    public void rent();    public void hello(String str);}

定义一个目标类实现上面的接口

  • 被代理类
public class RealSubject implements Subject {    @Override    public void rent() {        System.out.println("I want to rent my house");    }    @Override    public void hello(String str) {        System.out.println("hello: " + str);    }}
  • 动态代理类
    动态代理类必须要实现InvocationHandler接口,重写invoke接口。
public class DynamicProxy implements InvocationHandler {    // 这个就是我们要代理的目标对象    private Object subject;    // 通过构造方法,注入目标对象    public DynamicProxy(Object subject) {        this.subject = subject;    }    @Override    public Object invoke(Object object, Method method, Object[] args) throws Throwable {        // 在代理目标对象前我们可以添加一些自己的操作        System.out.println("before rent house");        System.out.println("Method:" + method);       /**         * 当代理对象调用目标对象的方法时,其会自动的跳转到代理对象关联的        * handler对象的invoke方法来进行调用        */        method.invoke(subject, args);        // 在代理目标对象后我们也可以添加一些自己的操作        System.out.println("after rent house");        return null;    }}
  • 场景类
public class Client {    public static void main(String[] args) {        // 我们要代理的目标对象        Subject realSubject = new RealSubject();        // 注入目标对象,最后是通过该目标对象来调用其方法的        InvocationHandler handler = new DynamicProxy(realSubject);       /**        * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数        *         * 第一个参数 realSubject.getClass().getClassLoader() ,        * 我们这里使用目标类的ClassLoader对象来加载我们的代理对象        *         * 第二个参数realSubject.getClass().getInterfaces(),        * 我们这里为代理对象提供的接口是目标对象所实现的接口,        * 这样我就能调用这组接口中的方法了        *         * 第三个参数handler,我们这里将这个代理对象关联到了上方的 InvocationHandler这个对象上        */      Subject subject = (Subject)Proxy.newProxyInstance(                                      realSubject.getClass().getClassLoader(),                                       realSubject.getClass().getInterfaces(),                                      handler);        System.out.println(subject.getClass().getName());        subject.rent();//转到对InvacationHandler接口的实现类的invoke()方法的调用        subject.hello("world");    }}

我们看控制台输出:

com.sun.proxy.$Proxy0

before rent house
Method:public abstract void com.dynamicproxy.Subject.rent()
I want to rent my house
after rent house

before rent house
Method:public abstract void com.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after rent house

至此,动态代理的实现我们就完成了。

在创建动态代理对象时

Subject subject = (Subject)Proxy.newProxyInstance(                                      realSubject.getClass().getClassLoader(),                                       realSubject.getClass().getInterfaces(),                                      handler);

由于我们给newProxyInstance()传入的第二个参数是realSubject的接口Subject,所以可以把动态生成的代理对象强转成Subject。

下面我们看这两个方法的调用:

subject.rent();subject.hello("world");

subject是动态生成的代理对象,当代理对象调用实现的接口中的方法时,就会关联到的 handler 中的invoke方法去执行,发起对目标类同样方法的调用。

通过打印结果我们看到,在通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:

Method:public abstract void com.dynamicproxy.Subject.rent()Method:public abstract void com.dynamicproxy.Subject.hello(java.lang.String)

正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,实际就是由其关联到的 handler 对象的invoke方法来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

JDK动态代理底层探究

经过上面的介绍,我们知道了代理对象代理目标对象的关键在于InvocationHandler接口,由该接口将代理对象、目标对象和自己增加的一些代理逻辑连接在一起,如下图是接口调用的执行过程:

动态代理执行过程

下面我们要探究一下代理对象时如何生成的

1). 代理对象的生成过程

从上一小节我们可以知道Java Proxy在构建代理对象的过程当中是构建了一个新的Class,其构建过程如下:

  1. Proxy基于代理接口获取其代理Class
    1.1 从缓存中获取,如果没有则继续下一步
    1.2 调用ProxyGenerator构建代理Class字节
    1.3 调用本地方法装载Class字节至当前ClassLoader
    1.4 返回新的Class对象

  2. 使用反射生成代理对象

  3. 调用代理对象

代理对象生成的时序图:

时序图

好,下面我们进入JDK的源码看一下:

既然生成代理对象是用的Proxy类的静态方newProxyInstance,那么我们就去它的源码里看一下它到底都做了些什么

   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);        }        /*         * 这里是生成Class的地方         */        Class<?> cl = getProxyClass0(loader, intfs);        /*         * 使用我们实现的InvocationHandler作为参数调用构造方法来获得代理类的实例           */        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;                    }                });            }            //利用构造函数进行初始化            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);        }    }

其中newInstance只是调用Constructor.newInstance来构造相应的代理类实例,这里重点是看getProxyClass0这个方法的实现:

private static Class<?> getProxyClass0(ClassLoader loader,                                           Class<?>... interfaces) {        //代理的接口数量不能超过65535                                           if (interfaces.length > 65535) {            throw new IllegalArgumentException("interface limit exceeded");        }       /**        * JDK对代理进行了缓存,如果已经存在相应的代理类,        * 则直接返回,否则才会通过ProxyClassFactory来创建代理         */        return proxyClassCache.get(loader, interfaces);    }

具体的缓存逻辑不探究那么细了,我们只关心ProxyClassFactory是如何生成代理类的,ProxyClassFactory是Proxy的一个静态内部类,实现接口BiFunction的apply方法:

    private static final class ProxyClassFactory        implements BiFunction<ClassLoader, Class<?>[], Class<?>>    {        // 所有代理类名字的前缀         private static final String proxyClassNamePrefix = "$Proxy";        // 用于生成代理类名字的计数器          private static final AtomicLong nextUniqueNumber = new AtomicLong();        @Override        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {            // 省略验证代理接口的代码……             String proxyPkg = null;     // 生成的代理类的包名             //对于非公共接口,代理类的包名与接口的相同              for (Class<?> intf : interfaces) {                int flags = intf.getModifiers();                if (!Modifier.isPublic(flags)) {                    accessFlags = Modifier.FINAL;                    String name = intf.getName();                    int n = name.lastIndexOf('.');                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));                    if (proxyPkg == null) {                        proxyPkg = pkg;                    } else if (!pkg.equals(proxyPkg)) {                        throw new IllegalArgumentException(                            "non-public interfaces from different packages");                    }                }            }            if (proxyPkg == null) {                // 对于公共接口的包名,默认为com.sun.prox                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";            }            // 获取计数             long num = nextUniqueNumber.getAndIncrement();            // 默认情况下,代理类的完全限定名为:            //com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1……依次递增              String proxyName = proxyPkg + proxyClassNamePrefix + num;             //生成代理类的字节码的地方            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                proxyName, interfaces, accessFlags);            try {                //根据二进制字节码返回相应的Class实例                 return defineClass0(loader, proxyName,                                    proxyClassFile, 0, proxyClassFile.length);            } catch (ClassFormatError e) {                throw new IllegalArgumentException(e.toString());            }        }    }

2) InvocationHandler的invoke方法调用

经过上面的源码跟踪我们对于JDK是怎样动态生成代理类的原理就分析的差不多了,搞清楚代理类的生成之后,我们再来分析一下是由谁来调用InvocationHandler的invoke方法的。要解决这个问题就要看一下JDK到底为我们生成了一个什么东西。用以下代码可以获取到JDK为我们生成的字节码并写到硬盘中。

工程目录:
工程目录

public class JavaProxyTest {    @Test    public void buildProxyClass() throws Exception {        byte[] bytes = ProxyGenerator.generateProxyClass("UserService$Proxy",new Class[]{UserService.class});        String fileName = System.getProperty("user.dir")+"/target/UserService$Proxy.class";        File file = new File(fileName);        FileOutputStream outputStream = new FileOutputStream(file);        outputStream.write(bytes);        outputStream.flush();        outputStream.close();    } }

获取到生成的Class文件,并反编译如下:

public final class UserService$Proxy extends Proxy implements UserService {    private static Method m1;    private static Method m2;    private static Method m0;    private static Method m3;    // 构造方法,参数就是刚才传过来的MyInvocationHandler类的实例    public UserService$Proxy(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);        }    }    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);        }    }     /**      * 这个方法是关键部分      */    public final void getName(String var1) throws  {        try {           /**            * 实际上就是调用InvocationHandler的            * public Object invoke(Object proxy, Method method, Object[] args)方法,            * 这样我们就搞清楚了invoke方法的调用。            */            super.h.invoke(this, m3, new Object[]{var1});        } catch (RuntimeException | Error var3) {            throw var3;        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);        }    }     // 在静态代码块中获取了4个方法:Object中的equals方法、UserService中的getName方法、     // Object中的hashCode方法、Object中toString方法      static {        try {            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));            m2 = Class.forName("java.lang.Object").getMethod("toString");            m0 = Class.forName("java.lang.Object").getMethod("hashCode");            m3 = Class.forName("com.proxy.UserService").getMethod("getName", Class.forName("java.lang.String"));        } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());        } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());        }    }}

到目前为止,我们对于Proxy代理对象的生成以及InvocationHandler的invoke方法的调用就分析结束了。

原创粉丝点击