Java动态代理之一CGLIB详解

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Java动态代理之一CGLIB详解

image.png在上篇文章《Java代理模式及动态代理详解》中我们介绍了Java中的静态代理模式与动态代理模式,并以JDK原生动态代理作为示例进行讲解。本篇文章我们来介绍一下基于CGLIB实现的动态代理,并与原生动态代理进行对比。

CGLIB介绍

CGLIB(Code Generation Library)是一个开源、高性能、高质量的Code生成类库(代码生成包)。

它可以在运行期扩展Java类与实现Java接口。Hibernate用它实现PO(Persistent Object 持久化对象)字节码的动态生成,Spring AOP用它提供方法的interception(拦截)。

CGLIB的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。但不鼓励大家直接使用ASM框架,因为对底层技术要求比较高。

使用实例

首先,引入CGLIB的依赖:






<dependency>    <groupId>cglib</groupId>    <artifactId>cglib</artifactId>    <version>3.2.5</version></dependency>

这里我们以操作用户数据的UserDao为例,通过动态代理来对其功能进行增强(执行前后添加日志)。UserDao定义如下:












public class UserDao {
  public void findAllUsers(){    System.out.println("UserDao 查询所有用户");  }
  public String findUsernameById(int id){    System.out.println("UserDao 根据ID查询用户");    return "公众号:程序新视界";  }}

创建一个拦截器,实现接口net.sf.cglib.proxy.MethodInterceptor,用于方法的拦截回调。















































import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/** * @author sec * @version 1.0 * @date 2020/3/24 8:14 上午 **/public class LogInterceptor implements MethodInterceptor {
  /**   *   * @param obj 表示要进行增强的对象   * @param method 表示拦截的方法   * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double   * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用   * @return 执行结果   * @throws Throwable 异常   */  @Override  public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {    before(method.getName());    // 注意这里是调用invokeSuper而不是invoke,否则死循环;    // methodProxy.invokeSuper执行的是原始类的方法;    // method.invoke执行的是子类的方法;    Object result = methodProxy.invokeSuper(obj, objects);    after(method.getName());    return result;  }
  /**   * 调用invoke方法之前执行   */  private void before(String methodName) {    System.out.println("调用方法" + methodName +"之【前】的日志处理");  }
  /**   * 调用invoke方法之后执行   */  private void after(String methodName) {    System.out.println("调用方法" + methodName +"之【后】的日志处理");  }}

实现MethodInterceptor接口的intercept方法。该方法中参数:

  • obj:表示要进行增强的对象;
  • method:表示要被拦截的方法;
  • objects:表示要被拦截方法的参数;
  • methodProxy:表示要触发父类的方法对象。

在方法的内部主要调用的methodProxy.invokeSuper,执行的原始类的方法。如果调用invoke方法否会出现死循环。

客户端使用示例如下:





















import net.sf.cglib.proxy.Enhancer;
public class CglibTest {
  public static void main(String[] args) {
    // 通过CGLIB动态代理获取代理对象的过程    // 创建Enhancer对象,类似于JDK动态代理的Proxy类    Enhancer enhancer = new Enhancer();    // 设置目标类的字节码文件    enhancer.setSuperclass(UserDao.class);    // 设置回调函数    enhancer.setCallback(new LogInterceptor());    // create方法正式创建代理类    UserDao userDao = (UserDao) enhancer.create();    // 调用代理类的具体业务方法    userDao.findAllUsers();    userDao.findUsernameById(1);  }}

执行客户端的main方法打印结果如下:







调用方法findAllUsers之【前】的日志处理UserDao 查询所有用户调用方法findAllUsers之【后】的日志处理调用方法findUsernameById之【前】的日志处理UserDao 根据ID查询用户调用方法findUsernameById之【后】的日志处理

可以看到,我们方法前后已经被添加上对应的“增强处理”。

反编译class

在main方法内的第一行我们也可以添加如下设置,来存储代理类的class文件。




// 代理类class文件存入本地磁盘,可反编译查看源码System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zzs/temp");

再次执行程序,我们可以看到在对应目录下生成三个class文件:




UserDao$$EnhancerByCGLIB$$1169c462.classUserDao$$EnhancerByCGLIB$$1169c462$$FastClassByCGLIB$$22cae79c.classUserDao$$FastClassByCGLIB$$197ae7fa.class

部分反编译代码如下:




























































import java.lang.reflect.Method;import net.sf.cglib.core.ReflectUtils;import net.sf.cglib.core.Signature;import net.sf.cglib.proxy.Callback;import net.sf.cglib.proxy.Factory;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;
public class UserDao$$EnhancerByCGLIB$$1169c462 extends UserDao implements Factory {    private boolean CGLIB$BOUND;    public static Object CGLIB$FACTORY_DATA;    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;    private static final Callback[] CGLIB$STATIC_CALLBACKS;    private MethodInterceptor CGLIB$CALLBACK_0;    private static Object CGLIB$CALLBACK_FILTER;    private static final Method CGLIB$findAllUsers$0$Method;    private static final MethodProxy CGLIB$findAllUsers$0$Proxy;    private static final Object[] CGLIB$emptyArgs;    private static final Method CGLIB$findUsernameById$1$Method;    private static final MethodProxy CGLIB$findUsernameById$1$Proxy;    private static final Method CGLIB$equals$2$Method;    private static final MethodProxy CGLIB$equals$2$Proxy;    private static final Method CGLIB$toString$3$Method;    private static final MethodProxy CGLIB$toString$3$Proxy;    private static final Method CGLIB$hashCode$4$Method;    private static final MethodProxy CGLIB$hashCode$4$Proxy;    private static final Method CGLIB$clone$5$Method;    private static final MethodProxy CGLIB$clone$5$Proxy;
    public final void findAllUsers() {        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;        if (var10000 == null) {            CGLIB$BIND_CALLBACKS(this);            var10000 = this.CGLIB$CALLBACK_0;        }
        if (var10000 != null) {            var10000.intercept(this, CGLIB$findAllUsers$0$Method, CGLIB$emptyArgs, CGLIB$findAllUsers$0$Proxy);        } else {            super.findAllUsers();        }    }
    final String CGLIB$findUsernameById$1(int var1) {        return super.findUsernameById(var1);    }
    public final String findUsernameById(int var1) {        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;        if (var10000 == null) {            CGLIB$BIND_CALLBACKS(this);            var10000 = this.CGLIB$CALLBACK_0;        }
        return var10000 != null ? (String)var10000.intercept(this, CGLIB$findUsernameById$1$Method, new Object[]{new Integer(var1)}, CGLIB$findUsernameById$1$Proxy) : super.findUsernameById(var1);    }
    // ...}

从反编译的源码可以看出,代理对象继承UserDao,拦截器调用intercept()方法,intercept()方法由自定义的LogInterceptor实现,所以最后调用LogInterceptor中的intercept()方法,从而完成了由代理对象访问到目标对象的动态代理实现。

CGLIB创建动态代理类过程

(1)查找目标类上的所有非final的public类型的方法定义;

(2)将符合条件的方法定义转换成字节码;

(3)将组成的字节码转换成相应的代理的class对象;

(4)实现MethodInterceptor接口,用来处理对代理类上所有方法的请求。

JDK动态代理与CGLIB对比

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象。

CGLIB动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy的优势:

最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。

基于CGLIB的优势:

无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。高性能。

小结

关于动态代理相关的实现就讲这么多,在具体的业务场景中如何选择可根据它们的优缺点进行针对性的比对。不过作为Java领域的标杆项目Spring,很多功能都选择使用CGLIB来实现。

《SpringBoot视频教程全家桶》的视频课程第一阶段已经录制完成,目前110节课程。后续还会不断新增其他实战场景、组件的内容。同时也会不断补充像本篇文章这样的实战经验。

课程链接:https://edu.csdn.net/course/detail/20369

原创推荐

Spring Boot中JdbcTemplate多数据源配置

Java14发布,16大新特性,代码更加简洁明快

Mysql连接数据库异常汇总【必收藏】

Java代理模式及动态代理详解

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
6月前
|
缓存 监控 Java
java动态代理
本文介绍了Java中的动态代理及其优势,通过增强原有方法或拦截调用实现无侵入式代码扩展,如添加日志、缓存等。文章先讲解了静态代理的基本概念和实现方式,随后引出动态代理解决静态代理在多方法、多类场景下的局限性。通过JDK提供的InvocationHandler接口和Proxy类,展示了如何动态生成代理对象。最后,文章还探讨了代理Hook技术,包括寻找Hook点、选择代理方式以及替换原始对象的具体步骤。
186 0
|
8月前
|
Dubbo Java 应用服务中间件
Java的动态代理
Java动态代理是一种强大的机制,允许在运行时创建接口的代理实例,并拦截方法调用。其核心组件包括`java.lang.reflect.Proxy`和`java.lang.reflect.InvocationHandler`。通过自定义接口、实现接口、编写`InvocationHandler`处理器并生成代理对象,可以灵活地增强方法功能,如日志记录、事务管理等。典型应用场景包括AOP、RPC、延迟加载和Mock测试。与CGLIB相比,JDK动态代理基于接口,性能稍慢但无需第三方库,适用于需要无侵入式增强的场合。
111 2
|
8月前
|
Java API 数据安全/隐私保护
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
206 0
探索Java动态代理的奥秘:JDK vs CGLIB
|
11月前
|
Java
JAVA 静态代理 & 动态代理
【11月更文挑战第14天】静态代理是一种简单的代理模式实现,其中代理类和被代理类的关系在编译时已确定。代理类实现与被代理类相同的接口,并持有被代理类的实例,通过调用其方法实现功能增强。优点包括代码结构清晰,易于理解和实现;缺点是对于多个被代理类,需为每个类编写相应的代理类,导致代码量大增,维护成本高。动态代理则在运行时动态生成代理类,更加灵活,减少了代码冗余,但可能引入性能损耗和兼容性问题。
106 0
|
12月前
|
Java
Java代码解释静态代理和动态代理的区别
### 静态代理与动态代理简介 **静态代理**:代理类在编译时已确定,目标对象和代理对象都实现同一接口。代理类包含对目标对象的引用,并在调用方法时添加额外操作。 **动态代理**:利用Java反射机制在运行时生成代理类,更加灵活。通过`Proxy`类和`InvocationHandler`接口实现,无需提前知道接口的具体实现细节。 示例代码展示了两种代理方式的实现,静态代理需要手动创建代理对象,而动态代理通过反射机制自动创建。
|
12月前
|
设计模式 缓存 Java
从源码学习Java动态代理|8月更文挑战
从源码学习Java动态代理|8月更文挑战
105 0
|
12月前
|
Java
深入理解Java动态代理
深入理解Java动态代理
138 1
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
1019 0
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
470 0
|
Java 数据库连接
Java之jdk和CGLib实现动态代理
Java之jdk和CGLib实现动态代理
167 0