• Java之动态代理简介


    图截于《大话设计模式》

    Proxy模式是常用的设计模式,其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

    用户可以更加结构图,自己编码完成Proxy模式。这种实现称为静态代理。

    Java提供了java.lang.reflect.Proxy类与InvocationHandler接口,配合反射,可以实现动态代理。静态代理的代理类与代理操作,都是事先编码,运行过程种无法修改代理结构。动态代理的代理与代理操作,都是在运行过程中,动态生成,可以在运行过程中,修改代理结构。

    InvocationHandler类提供代理操作行为,动态构建的代理类使用该接口调用代理操作。

    Proxy类主要负责动态构建代理类,有以下静态方法:

    • InvocationHandler getInvocationHandler(Object proxy)
    • Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
    • boolean isProxyClass(Class<?> cl)
    • Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

    调用getProxyClass()会动态生成Proxy类的子类,并使用loader参数指定的类加载器加载;第二个参数interfaces指定该子类将要继承的接口,可以指定多个接口。

    interface Foo {

    void funcA();

    }

       

    Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

    System.out.println(proxyClass.getName());

    for (Class<?> interfaceType : proxyClass.getInterfaces())

    System.out.println(" " + interfaceType);

       

    Class<?> proxyClassB = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class, AutoCloseable.class);

    System.out.println(proxyClassB.getName());

    for (Class<?> interfaceType : proxyClassB.getInterfaces())

    System.out.println(" " + interfaceType);

    输出如下:

    testproxy.$Proxy0

    interface testproxy.Foo

    testproxy.$Proxy1

    interface testproxy.Foo

    interface java.lang.AutoCloseable

    由输出可以推测,调用Proxy.getProxyClass()后,生成子类为:

    $Proxy* extends Proxy implements interfasces

    *为从0开始编号的整数,代表是第几个被创建的Proxy的子类。interfaces是Proxy.getProxyClass()方法的第二个不定参数。这些类的字节码创建在内存中。例如上面代码生成的子类为:

    $Proxy0 extends Proxy implements Foo {...}

    $Proxy1 extends Proxy implements Foo, AutoCloseable {...}

       

    实现同接口的Proxy子类,只会被创建一次,拥有共同的Class实例。

    Class<?> proxyClassA = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

    Class<?> proxyClassB = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

    System.out.println(proxyClassA.getName());

    System.out.println(proxyClassB.getName());

    输出如下:

    testproxy.$Proxy0

    testproxy.$Proxy0

       

    由于Proxy类只有一个带InvocationHandler接口的参数,所有所以需要获取指定版本的构造函数后,传入InvocationHandler接口的实现类获取动态子类的实例。

    Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class)

    .newInstance(handler);

    Proxy类提供的Proxy.newProxyInstance(),一步到位,简化了获取动态子类实例的操作。

    Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),

    new Class<?>[] { Foo.class },

    handler)

    fooProxy的实例为"$Proxy0 extends Proxy implements Foo {...}"类

       

    InvocationHandler接口有一个invoke()方法需要实现,动态代理类正式调用这个方法执行代理操作。举个例子:

    class SimpleHandler implements InvocationHandler {

    final private Object target;

    public SimpleHandler(Object target) {

    // *需要保留委托类的引用

    this.target = target;

    }

    @Override

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

    System.out.println("---Proxy before---");

    // *不适用第一个参数proxy,使用构造函数保留的引用调用委托类方法

    Object result = method.invoke(target, args);

    System.out.println("---Proxy end---");

    return result;

    }

    }

    使用以下委托类和代码测试。

    class FooImp implements Foo {

    @Override

    public void funcA() {

    System.out.println("FooImp");

    }

    }

       

    // Test

    Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),

    new Class<?>[] { Foo.class },

    new SimpleHandler(new FooImp()));

    fooProxy.funcA();

    System.out.println(fooProxy.toString());

    输出如下:

    ---Proxy before---

    FooImp

    ---Proxy end---

    ---Proxy before---

    ---Proxy end---

    testproxy.FooImp@4aa298b7

    fooProxy的是实例是Proxy类的动态子类,调用该子类的funcA()方法时,实际上先调用了invoke()方法,再通过反射调用委托类的funcA()方法。

       

    为什么toString()方法也被代理了?

    实际上,Object的equal()、hashCode()也同时被代理的,这个与Proxy子类的构建过程有关,可以参考资料《JDK动态代理实现原理》。

       

    实现InvocationHandler接口时,红色的注释指出两个疑点:

    • 为什么要保留委托类实例的引用?
    • 为什么不使用第一个参数proxy?

    这个两个问题其实是一个问题,invoke()函数的第一个参数proxy引用的实例是Proxy类的动态子类,而不是委托类。如果使用反射调用动态子类的方法,又会再次调用invoke()函数,陷入无限循环中,直到内存移除崩溃。因此,需要保留委托类的引用,让invoke()方法可以调用到委托类的方法。这样又引出一个问题,proxy参数又什么用?

    proxy参数可以做为返回值,实现方法链。具体可以参考《Understanding "proxy" arguments of the invoke method of java.lang.reflect.InvocationHandler

  • 相关阅读:
    「mysql优化专题」90%程序员面试都用得上的索引优化手册(5)
    「mysql优化专题」你们要的多表查询优化来啦!请查收(4)
    「mysql优化专题」单表查询优化的一些小总结,非索引设计(3)
    mysql优化专题」90%程序员都会忽略的增删改优化(2)
    「mysql优化专题」这大概是一篇最好的mysql优化入门文章(1)
    zookeeper
    linux查看进程是否存在,不存在则重启
    mysql导出文本文件,加分隔符
    oracle查看表空间和物理文件大小
    oracle插入
  • 原文地址:https://www.cnblogs.com/foundkey/p/9795151.html
Copyright © 2020-2023  润新知