暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java如何实现动态代理

来搞笑的Yuan 2020-11-25
476

代理模式是一种常见的设计模式,目的就是提供一个代理控制对目标对象的访问,在被代理对象方法执行前后进行一些处理。本篇主要探究JDK自带的动态代理实现。

1.代理模式

代理模式的UML图如下:

通常代理类和被代理类会实现相同接口,对于Client来说没有任何差别,在代理类内部调用了被代理类的方法,来实现代理的操作。

2.Proxy类

Java的Proxy类实现了代理模式,我们通过JavaApi文档中的例子来理解Proxy是如何实现代理的

InvocationHandler handler = new MyInvocationHandler(...);
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
             newInstance(handler);

可以看到Jdk实现动态代理的方式,步骤如下:

  1. 创建InvocationHandler实现代理逻辑

  2. 根据被代理类的类加载器和接口创建获取代理类

  3. 通过包含InvocationHandler构造函数创建代理类实例

Proxy类将这三步进行了包装,newProxyInstance()方法就是做这件事情,这使得使用动态代理变得非常容易。

Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                          new Class<?>[] { Foo.class },
                                          handler);

看下InvocationHandler类的注释

/**
 * 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.
 */
 
//执行一个委托给代理实例的方法并返回结果。当绑定的proxy实例调用方法时,将会调用InvocationHandler的这个方法
public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

3.示例

下面我们通过一个示例展示如何使用动态代理。

首先,新建一个接口Bird

public interface Bird {
    void fly();
}

其次,建立一个实现类Pigeon,我们实现一个代理,这个代理是对任意Bird的子类,都能增加“唱歌”的功能,如下

public class Pigeon implements Bird {
    @Override
    public void fly(
{
        System.out.println("信鸽飞行");
    }

    public static void main(String[] args{
        Bird bird = new Pigeon();
        Bird proxy = (Bird) Proxy.newProxyInstance(bird.getClass().getClassLoader(), bird.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
{
                System.out.println(proxy.getClass().getName());
                System.out.println("唱歌");
                method.invoke(bird, args);
                return null;
            }
        });
        System.out.println("------");
        proxy.fly();
        System.out.println("------");
    }
}

运行main,结果如下:

------
com.sun.proxy.$Proxy0
唱歌
信鸽飞行
------

4.Proxy类的真相

上述结果中我打印了生成的代理类的类名,如上是com.sun.proxy.$Proxy0
,通过查看Proxy类生成的代理类具体是什么,能更容易理解InvocationHandler的调用时机。因为代理类是运行时生成的(通过反射),我们借助工具来获取这个代理类的源码。

我们在main函数前加上一句配置

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

同时在IDEA中安装反编译工具Java Bytecode Decompiler
,执行main函数后,可以看到新生成了一个文件,打开如下:

public final class $Proxy0 extends Proxy implements Bird {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    //注入InvocationHandler的构造函数
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

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

    //省略无用代码

}

可以看到代理类中有注入InvocationHandler的构造函数,和一个与接口相同的方法,用于调用InvocationHandler的invoke
方法,执行代理逻辑。

5.总结

JDK的Proxy类近乎完美地实现了动态代理,唯一的限制是,被代理类需要实现某个接口
,通过查阅Proxy.ProxyClassFactory中的源码可以知道,这是因为生成代理类时,需要通过反射,根据接口的方法来生成代理类中的方法。

Cglib基于Java字节码操作框架ASM实现了基于普通类的动态代理。在Spring框架中对两种动态代理都有使用,有空再详细聊聊Cglib的使用和原理。

文章转载自来搞笑的Yuan,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论