详解Java动态代理机制

     之前介绍的反射和注解都是Java中的动态特性,还有即将介绍的动态代理也是Java中的一个动态特性。这些动态特性使得我们的程序很灵活。动态代理是面向AOP编程的基础。通过动态代理,我们可以在运行时动态创建一个类,实现某些接口中的方法,目前为止该特性已被广泛应用于各种框架和类库中,例如:Spring,Hibernate,MyBatis等。理解动态代理是理解框架底层的基础。      主要内容如下:

  • 理解代理是何意
  • Java SDK实现动态代理
  • 第三方库cglib实现动态代理

一、代理的概念      单从字面上理解,代理就是指原对象的委托人,它不是原对象但是却有原对象的权限。Java中的代理意思类似,就是指通过代理来操作原对象的方法和属性,而原对象不直接出现。这样做有几点好处:

  • 节省创建原对象的高开销,创建一个代理并不会立马创建一个实际原对象,而是保存一个原对象的地址,按需加载
  • 执行权限检查,保护原对象

实际上代理堵在了原对象的前面,在代理的内部往往还是调用了原对象的方法,只是它还做了其他的一些操作。下面看第一种实现动态代理的方式。

二、Java SDK实现动态代理      实现动态代理主要有如下几个步骤:

  • 实现 InvocationHandler接口,完成自定义调用处理器
  • 通过Proxy的getProxyClass方法获取对应的代理类
  • 利用反射技术获取该代理类的constructor构造器
  • 利用constructor构造代理实例对象

在一步步解析源码之前,我们先通过一个完整的实例了解下,整个程序的一步步逻辑走向。

//定义了一个调用处理器
public class MyInvotion implements InvocationHandler {

    private Object realObj;

    public MyInvotion(Object obj){
        this.realObj =  obj;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        //通过代理执行原对象的方法
        return method.invoke(realObj,args);
    }
}
//定义一个接口
public interface MyInterface {
    
    public void sayHello();
}

//该接口的一个实现类,该类就是我们的原对象
public class ClassA implements MyInterface {

    public void sayHello(){
        System.out.println("hello walker");
    }
}
//main 函数
public static void main(String[] args){
        ClassA a = new ClassA();
        MyInvotion myInvotion = new MyInvotion(a);
        Class myProxy = Proxy.getProxyClass(ClassA.class.getClassLoader(), new Class[]{MyInterface.class});
        Constructor constructor =myProxy.getConstructor(new Class[]{InvocationHandler.class});
        MyInterface m = (MyInterface)constructor.newInstance(myInvotion);
        m.sayHello();
    }
输出结果:hello walker

简单说下整体的运行过程,首先我们创建ClassA 实例并将它传入自定义的调用处理器MyInvotion,在MyInvotion中用realObj接受该参数代表原对象。接着调用Proxy的getProxyClass方法,将ClassA 的类加载器和ClassA 的实现的接口集合传入,该方法内部会实现所有接口返回该类的代理类,然后我们利用反射获取代理类的构造器并创建实例。

以上便是整个程序运行的大致流程,接下来我们从源代码的角度看看具体是如何实现的。首先我们看InvocationHandler接口,这是我们的调用处理器,在代理类中访问的所有的方法都会被转发到这执行,具体的等我们看了代理类源码及理解了。该接口中唯一的方法是:

public Object invoke(Object proxy, Method method, Object[] args)
  • 参数Proxy表示动态生成的代理类的对象,基本没啥用
  • 参数method表示当前正在被调用的方法
  • 数组args指定了该方法的参数集合

我们上例中对该接口的实现情况,定义了一个realObj用于保存原对象的引用。重写的invoke方法中调用了原对象realObj的method方法,具体谁来调用该方法以及传入的参数是什么,在看完代理类源码即可知晓。

接下来我们看看最核心的内容,如何动态创建代理类。这是getProxyClass方法的源码:

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

        return 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);
    }

判断如果该类的接口超过65535(想必还没有那么牛的类),抛出异常。在我们的Proxy类中有个属性proxyClassCache,这是一个WeakCache类型的静态变量。它指示了我们的类加载器和代理类之间的映射。所以proxyClassCache的get方法用于根据类加载器来获取Proxy类,如果已经存在则直接从cache中返回,如果没有则创建一个映射并更新cache表。具体创建一个Proxy类并存入cache表中的代码限于能力,未能参透。

至此我们就获取到了该ClassA类对应的代理类型,接着我们通过该类的getConstructor方法获取该代理类的构造器,并传入InvocationHandler.class作为参数,至于为何要传入该类型作为参数,等会看代理类源码变一目了然了。

最后newInstance创建该代理类的实例,实现对ClassA对象的代理。

可能看完上述的介绍,你还会有点晕。下面我们通过查看动态生成的代理类的源码来加深理解。上述getProxyClass方法会动态创建一个代理类并返回他的Class类型,这个代理类一般被命名为$ProxyN,这个N是递增的用于标记不同的代理类。我们可以利用反编译工具反编译该class:

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

    public $Proxy0(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }

    public final boolean equals(Object paramObject) {
        return ((Boolean) this.h.invoke(this, m1, 
                new Object[] { paramObject })).booleanValue();
    }

    public final void sayHello() {
        this.h.invoke(this, m3, null);
    }

    public final String toString() {
        return (String) this.h.invoke(this, m2, null);
    }

    public final int hashCode() {
        return ((Integer) this.h.invoke(this, m0, null)).intValue();
    }

    static {
        m1 = Class.forName("java.lang.Object").getMethod("equals",
                new Class[] { Class.forName("java.lang.Object") });
        m3 = Class.forName("laoma.demo.proxy.SimpleJDKDynamicProxyDemo$IService")
                .getMethod("sayHello",new Class[0]);
        m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    }
}

这就是上述的ClassA的动态代理类,我们看到该类的构造方法传入参数InvocationHandler类型,并调用了父类Proxy的构造方法保存了这个InvocationHandler实例,这也解释了我们为什么在获取构造器的时候需要指定参数类型为InvocationHandler,就是因为动态代理类只有一个构造器并且参数类型为InvocationHandler。

接着我们看其中的方法,貌似只有一个sayHello是我们知道的,别的方法哪来的?我们说过在动态创建代理类的时候,会实现原对象的所有接口。所以sayHello方法是实现的MyInterface。而其余的四个方法是代理类由于比较常用,被默认添加到其中。而这些方法的内部都是调用的this.h.invoke这个方法,this.h就是保存在父类Proxy中的InvocationHandler实例(我们用构造器向其中保存的),调用了这个类的invoke方法,在我们自定义的InvocationHandler实例中重写了invoke方法,我们写的比较简单,直接执行传入的method。

也就是我们调用代理类的任何一个方法都会转发到该InvocationHandler实例中的involve中,因为该实例中保存有我们的原对象,所以我们可以选择直接调取原对象中的方法作为回调。

以上便是有关Java SDK中动态代理的相关内容,稍微总结下,首先我们通过实现InvocationHandler自定义一个调用处理类,该类中会保存我们的原对象,并提供一个invoke方法供代理类使用。然后我们通过getProxyClass方法动态创建代理类,最后用反射获取代理类的实例对象。

需要注意的是:以上我们使用的四步创建代理实例时最根本的,其实Proxy中提供一个方法可以封装2到4步的操作。上述代码也可以这么写:

ClassA a = new ClassA();
MyInterface aProxy = (MyInterface)Proxy.newProxyInstance(ClassA.class.getClassLoader(),new Class<?>[]{MyInterface.class},new MyInvotion(a));
aProxy.sayHello();

我们打开该方法的内部源码,其实走的还是我们上述的过程,它就是做了封装。

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

    Objects.requireNonNull(h);
        
        //获取所有接口
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        
        //创建动态代理类
        Class<?> cl = getProxyClass0(loader, intfs);

        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});
    .............
    ..............
}

二、第三方库cglib实现动态代理      使用动态代理,我们编写通用的代码逻辑,即仅实现一个InvocationHandler实例完成对多个类型的代理。但是我们从动态生成的代理类的源码可以看到,所有的代理类都继承自Proxy这个类,这就导致我们这种方式不能代理类,只能代理接口。因为java中是单继承的。也就是说,给我们一个类型,我们只能动态实现该类所有的接口类型,但是该类继承的别的类我们在代理类中是不能使用的,因为它没有被代理类继承。下面看个例子:

public class ClassB {
    public void welcome(){
        System.out.println("welcom walker");
    }
}
public interface MyInterface {

    public void sayHello();
}

//需要被代理的原类型,继承了ClassB和接口MyInterface 
public class ClassA extends ClassB implements MyInterface {

    public void sayHello(){
        System.out.println("hello walker");
    }
}
//InvocationHandler 实例
public class MyInvotion implements InvocationHandler {

private Object realObj;

public MyInvotion(Object obj){
     this.realObj =  obj;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        return method.invoke(realObj,args);
    }
}

我们反编译该代理类,和上述的源码是一样的,此处不再重复贴出。我们能从中看出来的是,我们的代理只会实现原类型中所有的接口,至于原类型所继承的类,在生成Proxy代理类的时候会丢弃,因为所有的代理类必须继承Proxy类,这就导致原类型的父类中的方法 在代理类中丢失。这是该种方式的一大弊端。下面我们看看另一种方式实现动态代理,该种方式完美解决了这种不足。

限于篇幅,我们下篇介绍cglib实现动态代理机制的内容,本篇暂时结束,总结的不好,望海涵。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java社区

Java开发者容易犯的十个错误

1032
来自专栏青青天空树

struts返回json数据

  实际上就是在struts中获取response对象的输出流。然后写入你要返回的json数据,本质和用servlet返回json数据是一样的,需要自己导入js...

1016
来自专栏Java Edge

SpringMVC数据绑定定义支持的数据绑定方式

定义 百度百科定义: 简单绑定是将一个用户界面元素(控件)的属性绑定到一个类型(对象)实例上的某个属性的方法。 例如,如果一个开发者有一个Customer类...

3716
来自专栏jeremy的技术点滴

koa框架源码解读

3388
来自专栏小勇DW3

Conccrent中 Unsafe类原理 以及 原子类AutomicXX的原理以及对Unsafe类的使用

Java中基于操作系统级别的原子操作类sun.misc.Unsafe,它是Java中对大多数锁机制实现的最基础类。请注意,JDK 1.8和之前JDK版本的中su...

832
来自专栏Java技术分享

怎样理解 java 注解和运用注解编程?

完整源码下载地址 GitHub - MatrixSeven/JavaAOP: 一个基于原生JDK动态代理实现的AOP小例子 使用反射结合JDK动态代理实现了类似...

2529
来自专栏JAVA后端开发

给mybatis添加自动建表,自动加字段的功能

以前项目用惯了hibernate,jpa,它有个自动建表功能,只要在PO里加上配置就可以了,感觉很爽. 但现在用mybatis,发现没有该功能,每次都加个字段...

1653
来自专栏CodingToDie

个人开源项目 FastSQL

一个简单的数据库工具类,可以简化 DB 操作,减少 SQL 语句的书写,同时提供将 SQL 转换 Bean 和将 Bean转换 SQL 的方法,

792
来自专栏微信公众号:Java团长

Java集合类型详解

这篇文章总结了所有的Java集合(Collection)。主要介绍各个集合的特性和用途,以及在不同的集合类型之间转换的方式。

922
来自专栏企鹅号快讯

CharSequence与String

CharSequence和String是Java中两个不同的基本概念。本篇将介绍它们之间的差异与共性。CharSequenceCharSequence是一个表示...

22310

扫码关注云+社区