动态代理简单实现与原理分析

前言


本篇文章会结合实例和源码对JDK动态代理进行学习,并会在最后总结JDK动态代理与CGLIB动态代理的区别,以帮助更好的理解动态代理。

正文


一. 代理模式

在学习动态代理之前,先回顾一下设计模式中的代理模式。

代理模式定义为:给被代理对象提供一个代理对象以控制对被代理对象的访问,即访问对象不适合或者不能直接引用被代理对象时,代理对象作为访问对象和被代理对象之间的中介。

代理模式中,有三种角色,分别为抽象主题(AbstractSubject),真实主题(RealSubject )和代理(Proxy),三种角色含义如下表所示。

抽象主题(AbstractSubject

通过接口或者抽象类声明真实主题和代理需要实现的业务方法

真实主题(RealSubject

实现了抽象主题中的业务方法,是代理所代表的真实对象,是最终要引用的对象

代理(Proxy

实现了抽象主题,提供了与真实主题相同的方法,其内部含有对真实主题的引用,可以访问,控制或扩展真实主题的功能

代理模式的三种角色的关系用类图表示如下。

二. 静态代理

根据代理模式中的代理类的字节码文件的创建时机,可以将代理分为静态代理动态代理:静态代理在程序运行前,代理类的字节码文件就已经存在,而动态代理则是在程序运行期间JVM通过反射机制为代理类生成字节码文件。本小节以一个例子对静态代理进行学习。

定义抽象主题,如下所示。

publicinterfaceTestServiceA {

    voidexecuteTestA();
    voidsubmitTestA();

}

publicinterfaceTestServiceB {

    voidexecuteTestB();
    voidsubmitTestB();

}
复制代码

上述定义了两个接口作为抽象主题,下面定义一个真实主题来实现抽象主题,如下所示。

publicclassRealObjectimplementsTestServiceA, TestServiceB {

    @OverridepublicvoidexecuteTestA() {
        System.out.println("Test A execute.");
    }

    @OverridepublicvoidsubmitTestA() {
        System.out.println("Test A submit.");
    }

    @OverridepublicvoidexecuteTestB() {
        System.out.println("Test B execute.");
    }

    @OverridepublicvoidsubmitTestB() {
        System.out.println("Test B submit.");
    }

}
复制代码

再定义一个代理类,如下所示。

publicclassProxyObjectimplementsTestServiceA, TestServiceB {

    private RealObject realObject;

    publicProxyObject(RealObject realObject) {
        this.realObject = realObject;
    }

    @OverridepublicvoidexecuteTestA() {
        before();
        realObject.executeTestA();
        after();
    }

    @OverridepublicvoidsubmitTestA() {
        before();
        realObject.submitTestA();
        after();
    }

    @OverridepublicvoidexecuteTestB() {
        before();
        realObject.executeTestB();
        after();
    }

    @OverridepublicvoidsubmitTestB() {
        before();
        realObject.submitTestB();
        after();
    }

    privatevoidbefore() {
        System.out.println("Begin to do.");
    }

    privatevoidafter() {
        System.out.println("Finish to do.");
    }

}
复制代码

可以看到,真实主题RealObject和代理ProxyObject均实现了抽象主题,同时代理ProxyObject还持有真实主题RealObject的引用,因此需要通过ProxyObject才能访问到RealObject,同时ProxyObject在执行RealObject的方法时,还可以执行一些额外的逻辑来扩展RealObject的功能。编写一个客户端程序,如下所示。

publicclassClientOne {

    publicstaticvoidmain(String[] args) {
        RealObjectrealObject=newRealObject();
        ProxyObjectproxyObject=newProxyObject(realObject);
        proxyObject.executeTestA();
        proxyObject.submitTestA();
        proxyObject.executeTestB();
        proxyObject.submitTestB();
    }

}
复制代码

运行结果如下所示。

三. JDK动态代理

思考一下,在第二小节中的静态代理,在实际使用中,存在什么不足?这里归纳如下。

  • 如果让一个代理类代理多个被代理类,那么会导致代理类变得过大;

  • 如果每个被代理类都对应一个代理类,那么会导致代理类变得过多;

  • 由于被代理类和代理类都需要实现相同的接口,当接口定义的方法增加或减少时,被代理类和代理类需要一起修改,不易于代码维护。

上述静态代理存在的问题,可以由动态代理来解决,即在程序运行期间,才决定代理类的生成。下面先根据一个基于JDK动态代理的例子来说明动态代理的使用方法,然后再基于源码分析JDK动态代理的实现机制以及为什么可以动态的生成代理类。

JDK动态代理主要是基于两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler,所有基于JDK动态代理生成的代理类均会继承于Proxy,同时代理类会持有InvocationHandler的引用,而InvocationHandler中会持有被代理类的引用,因此可以将InvocationHandler理解为代理类与被代理类的中介。

首先创建一个类实现InvocationHandler接口,如下所示。

publicclassTestInvocationHandlerimplementsInvocationHandler {

    private Object realObject;

    publicTestInvocationHandler(Object realObject) {
        this.realObject = realObject;
    }

    @Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
        before();
        ObjectinvokeResult= method.invoke(realObject, args);
        after();
        return invokeResult;
    }

    privatevoidbefore() {
        System.out.println("Begin to do.");
    }

    privatevoidafter() {
        System.out.println("Finish to do.");
    }

}
复制代码

如上所示,TestInvocationHandler实现了InvocationHandler接口,同时TestInvocationHandler中有一个名为realObject的成员变量,该变量就是被代理类,当代理类执行代理方法时,就会通过TestInvocationHandler来调用被代理类的方法,同时TestInvocationHandler中还可以自己定义一些方法来实现功能扩展,在上面例子中就定义了before()after() 两个方法,分别用于在被代理类方法执行前和执行后做一些事情。

创建好了TestInvocationHandler之后,就可以开始创建动态代理类了,其中被代理类还是沿用第二小节中的RealObject,创建动态代理类的逻辑如下所示。

publicclassClientTwo {

    publicstaticvoidmain(String[] args) {
        // 保存动态代理类的字节码文件
        System.getProperties().setProperty(
                "sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        // 创建被代理类RealObjectrealObject=newRealObject();
        // 获取被代理类的类加载器ClassLoaderclassLoader= realObject.getClass()
                .getClassLoader();
        // 获取被代理类实现的接口的Class对象
        Class<?>[] interfaces = realObject.getClass()
                .getInterfaces();
        // 以被代理类作为入参创建InvocationHandlerInvocationHandlerinvocationHandler=newTestInvocationHandler(realObject);
        // 通过调用Proxy的newProxyInstance()方法创建动态代理对象ObjectproxyInstance= Proxy.newProxyInstance(
                classLoader, interfaces, invocationHandler);

        ((TestServiceA) proxyInstance).executeTestA();
        ((TestServiceA) proxyInstance).submitTestA();
        ((TestServiceB) proxyInstance).executeTestB();
        ((TestServiceB) proxyInstance).submitTestB();
    }

}
复制代码

运行上述程序,执行结果如下所示。

工程目录/com/sun/proxy下查看生成的代理类的字节码文件,反编译如下所示。

publicfinalclass$Proxy0extendsProxyimplementsTestServiceA, TestServiceB {

    privatestatic Method m1;
    privatestatic Method m3;
    privatestatic Method m2;
    privatestatic Method m6;
    privatestatic Method m5;
    privatestatic Method m0;
    privatestatic Method m4;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    publicfinalbooleanequals(Object var1)throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, newObject[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            thrownewUndeclaredThrowableException(var4);
        }
    }

    publicfinalvoidexecuteTestA()throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            thrownewUndeclaredThrowableException(var3);
        }
    }

    publicfinal String toString()throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            thrownewUndeclaredThrowableException(var3);
        }
    }

    publicfinalvoidexecuteTestB()throws  {
        try {
            super.h.invoke(this, m6, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            thrownewUndeclaredThrowableException(var3);
        }
    }

    publicfinalvoidsubmitTestB()throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            thrownewUndeclaredThrowableException(var3);
        }
    }

    publicfinalinthashCode()throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            thrownewUndeclaredThrowableException(var3);
        }
    }

    publicfinalvoidsubmitTestA()throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            thrownewUndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceA").getMethod("executeTestA");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m6 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceB").getMethod("executeTestB");
            m5 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceB").getMethod("submitTestB");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceA").getMethod("submitTestA");
        } catch (NoSuchMethodException var2) {
            thrownewNoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            thrownewNoClassDefFoundError(var3.getMessage());
        }
    }

}
复制代码

可以看到,生成的代理类,继承于Proxy,同时也实现了被代理类实现的接口,当代理类执行代理方法时,会通过其继承于ProxyInvocationHandler来调用到被代理类的真实方法,至此,JDK动态代理的一个例子就介绍到这里。现在看一下Proxy.newProxyInstance() 方法做了哪些事情,来搞明白为什么可以动态的生成代理类,方法源码如下所示。

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

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

    // 生成代理类的Class对象
    Class<?> cl = getProxyClass0(loader, intfs);

    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        // 获取代理类的构造器final Constructor<?> cons = cl.getConstructor(constructorParams);
        finalInvocationHandlerih= h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(newPrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    returnnull;
                }
            });
        }
        // 生成代理对象return cons.newInstance(newObject[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        thrownewInternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwablet= e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            thrownewInternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        thrownewInternalError(e.toString(), e);
    }
}
复制代码

getProxyClass0() 方法会生成代理类的Class对象,生成过的代理类的Class对象会缓存在Proxy的类变量proxyClassCache中,所以getProxyClass0() 方法会先在proxyClassCache中获取代理类Class对象,如果获取不到,则会通过ProxyClassFactory来生成代理类Class对象。

ProxyClassFactoryProxy的静态内部类,其主要完成两件事情。

  • 生成代理类的字节码文件;

  • 调用native方法defineClass0() 来解析代理类的字节码文件并生成代理类的Class对象。

ProxyClassFactory中生成代理类的字节码文件时,是调用的ProxyGeneratorgenerateProxyClass() 方法,并且在生成字节码文件前,会将ObjecthashCode()equals()toString() 方法以及被代理类实现的接口所定义的方法添加到代理类的方法中。

至此,可以对JDK动态代理如何动态生成代理类进行如下的图示归纳。

四. CGLIB动态代理

JDK动态代理中,要求被代理类需要实现接口,这一点限制了JDK动态代理的使用,当被代理类未实现接口时,想要动态生成代理类,可以使用CGLIB动态代理,使用CGLIB生成的代理类是被代理类的子类,本小节将结合例子对CGLIB的使用进行说明。

首先创建一个被代理类,如下所示。

publicclassRealService {

    publicvoidexecute(String flag) {
        System.out.println("Test " + flag + " execute.");
    }

    publicvoidsubmit(String flag) {
        System.out.println("Test " + flag + " submit.");
    }

}
复制代码

然后创建一个方法拦截器,方法拦截器需要继承于MethodInterceptor,用于在代理对象执行方法时进行拦截,如下所示。

publicclassTestInterceptorimplementsMethodInterceptor {

    /**
     * @param o 代理对象
     * @param method 被代理对象的方法
     * @param objects 被代理对象的方法参数类型
     * @param methodProxy 被代理对象的方法的代理
     */@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)throws Throwable {
        before();
        Objectresult= methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    privatevoidbefore() {
        System.out.println("Begin to do.");
    }

    privatevoidafter() {
        System.out.println("Finish to do.");
    }

}
复制代码

上述方法拦截器会在每一个代理对象的方法执行时进行拦截,然后依次执行before() 方法,被代理对象的方法和after() 方法,以达到对被代理对象的方法的增强效果,同时intercept() 方法的第一个参数是代理对象,所以想要执行被代理对象的方法需要使用invokeSuper()

最后创建客户端程序来测试效果,如下所示。

publicclassClientThree {

    publicstaticvoidmain(String[] args) {
        Enhancerenhancer=newEnhancer();
        enhancer.setSuperclass(RealService.class);
        enhancer.setCallback(newTestInterceptor());
        ObjectproxyObject= enhancer.create();

        ((RealService) proxyObject).execute("cglib");
        ((RealService) proxyObject).submit("cglib");
    }

}
复制代码

运行结果如下所示。

CGLIB动态代理也能保存代理类的字节码文件,只需要做如下设置。

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 保存路径);
复制代码

现在通过IDEA反编译字节码文件之后,可以查看生成的代理类,下面截取一部分来说明代理方法的调用和增强,如下所示。

publicclassRealService$$EnhancerByCGLIB$$64276695extendsRealServiceimplementsFactory {
    
    // ...staticvoid CGLIB$STATICHOOK1() {
        // ...
        CGLIB$execute$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "execute", "CGLIB$execute$0");
        // ...
    }

    // ...publicfinalvoidexecute(String var1) {
        MethodInterceptorvar10000=this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$execute$0$Method, newObject[]{var1}, CGLIB$execute$0$Proxy);
        } else {
            super.execute(var1);
        }
    }
    
    // ...

}
复制代码

查看反编译得到的代理类可以知道,CGLIB动态代理生成的代理类是被代理类的子类,以及当代理类调用方法时,会通过MethodInterceptor来调用被代理类的方法和增强方法。

至此,CGLIB动态代理的例子介绍完毕,相较于JDK动态代理,CGLIB动态代理是通过字节码处理框架ASM来动态生成代理类的字节码文件并加载到JVM中。

下面是JDK动态代理和CGLIB动态代理的一个简单对比。

JDK动态代理

  • JDK动态代理中,代理类调用被代理类的方法依赖InvocationHandler接口;

  • JDK动态代理要求被代理类需要实现一个或多个接口;

  • JDK动态代理是基于反射来动态生成代理类的字节码文件。

CGLIB动态代理

  • CGLIB动态代理中,代理类调用被代理类的方法依赖MethodInterceptor接口;

  • CGLIB动态代理要求被代理类不能为final,但不要求被代理类需要实现接口;

  • CGLIB动态代理无法为被代理类中的final方法进行代理;

  • CGLIB动态代理是基于ASM框架来动态生成代理类的字节码文件。

关于CGLIB代理对象执行方法的一个流程,将在后面专门使用一篇文章进行讲解。

总结


本篇文章对代理设计模式静态代理JDK动态代理CGLIB动态代理进行了讨论。

静态代理实现最为简单,在程序编译完成之后,代理类的字节码文件已经生成,可以直接被JVM加载到内存中,使用效率高,省去了动态代理中的生成字节码文件的时间,但是缺点就是静态代理中通常一个代理类只代理一个被代理类,如果被代理类过多,会导致代理类也过多。

JDK动态代理可以在程序运行期间基于反射来动态生成代理类的字节码文件,但是要求被代理类实现接口,这限制了JDK动态代理的使用场景。

CGLIB动态代理不要求被代理类实现接口,其底层是基于ASM框架来动态生成代理类的字节码文件,CGLIB创建的代理类是被代理类的子类,所以CGLIB动态代理要求被代理类不能是final的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Mybatis-Plus是在MyBatis框架的基础上进行封装的一款持久层框架。它主要提供了一些增强功能,使得在开发中能够更加方便、快捷地进行数据访问操作。 Mybatis-Plus的底层原理与MyBatis类似,都是基于Java反射和动态代理技术实现的。在运行时,Mybatis-Plus会通过扫描实体类的注解信息,自动生成对应的Mapper接口和SQL语句。 此外,Mybatis-Plus还提供了一些特殊的注解和配置,比如@TableId、@TableField等,用于处理实体类与数据库表之间的映射关系。同时,Mybatis-Plus也支持代码自动生成、性能分析和多租户等高级功能,使得开发者能够更加便捷地完成数据访问操作。 ### 回答2: Mybatis Plus是基于Mybatis框架的增强工具,可以简化和提升Mybatis的开发效率。它的原理主要可以分为三个方面: 1. 配置继承:Mybatis Plus通过配置继承来简化映射器接口的编写。在Mybatis中,我们需要为每个实体创建一个映射器接口,并在接口中定义SQL语句。而在Mybatis Plus中,我们只需要创建一个基础的映射器接口,然后通过继承该接口,自动获得基本的CRUD操作,无需再手动编写SQL语句。 2. 自动SQL解析与生成:Mybatis Plus具有自动解析和生成SQL的能力。它可以根据实体类的属性自动生成相应的SQL语句,包括查询条件、分页参数等。同时,它还支持自定义SQL片段,可以方便地进行复杂的查询操作。 3. 全局自动注入:Mybatis Plus可以自动注入一些通用的组件,如分页插件、性能分析插件等。我们只需要简单地配置一下,就可以享受到这些插件的功能,无需再手动编写和管理这些组件。 总的来说,Mybatis Plus的原理就是简化和提升Mybatis框架的开发效率,通过配置继承、自动SQL解析与生成、全局自动注入等技术手段,使我们能够更加简单、快捷地编写数据库访问代码。它是一个非常实用的框架,可以有效地提高开发效率和代码质量。 ### 回答3: MyBatis Plus是一个功能强大的持久层框架,它是在MyBatis基础之上进行扩展和增强的。其核心原理主要包括以下几个方面: 1. 代码生成器:MyBatis Plus提供了代码生成器工具,通过读取数据库表结构,生成相应的Java实体、Mapper接口和XML映射文件。这个工具大大简化了手动编写代码的工作量,提高了开发效率。 2. AR模式:Active Record(活动记录)模式是MyBatis Plus的特有功能之一。AR模式使得开发者可以直接通过实体对象进行数据库操作,无需手动编写SQL语句,简化了操作步骤,提高了代码的可读性。 3. 全局配置:MyBatis Plus提供了丰富的全局配置选项,包括自动填充字段、逻辑删除、SQL性能分析等。通过设置不同的配置项,可以灵活地满足不同项目的需求。 4. 继承BaseMapper:MyBatis Plus的Mapper接口默认继承了BaseMapper接口,其中封装了一系列基础的CRUD方法,使得开发者无需编写重复的增删改查SQL语句。同时,还可以通过自定义接口方法,实现更加复杂的查询操作。 5. 插件机制:MyBatis Plus提供了插件机制,允许开发者对框架进行扩展。开发者可以实现自定义的插件,并将其注册到MyBatis Plus中,以实现一些特定的功能,比如分页插件、数据权限控制插件等。 总之,MyBatis Plus通过这些原理和机制,大大简化了开发者的工作量,提高了开发效率和代码质量。同时,它与原生的MyBatis完美兼容,可以灵活地与其他框架进行集成,使用起来非常方便。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值