Java动态代理之一CGLIB详解

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

在上篇文章《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.class

UserDao$$EnhancerByCGLIB$$1169c462$$FastClassByCGLIB$$22cae79c.class

UserDao$$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来实现。


相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
8月前
|
Java API 数据安全/隐私保护
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
208 0
探索Java动态代理的奥秘:JDK vs CGLIB
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
72 0
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
76 0
|
设计模式 Java 数据安全/隐私保护
剖析代理模式及Java两种动态代理(JDK动态代理和CGLIB动态代理)
本文详述了代理模式以及我们经常接触到的两种具体实现(JDK动态代理和CGLIB动态代理),为读者理解代理模式、JDK动态代理和CGLIB动态代理提供帮助
316 0
剖析代理模式及Java两种动态代理(JDK动态代理和CGLIB动态代理)
|
设计模式 Java 数据库连接
Java 动态代理机制 (一) JDK Proxy详解
JDK Proxy 代理是可以根据我们的 接口 Interface 生成类的字节码,从而可以在 Java 中为所欲为的一种技术,包括对象增强(修改成员变量),函数增强(在函数前后执行别的代码),根据接口名执行不同逻辑 等。在 Mybatis 中有典型应用。它的本质是 由 Proxy 生成一个 代理对象,实现我们的接口。这个对象中有我们的回调函数。当调用 代理对象的接口方法时,这个对象再调用我们的回调函数,我们的回调函数再调用原对象的对应方法。从而实现代理。为了实现代理模式,Proxy 用了另外一种设计模式:命令模式。 不过,如果我们没有接口,直接是个类,那么 Proxy 将不能用,我们可能需
|
Java 数据库连接 Spring
Java动态代理模式jdk和cglib(下)
Java动态代理模式jdk和cglib(下)
112 0
|
Java
Java动态代理模式jdk和cglib(上)
Java动态代理模式jdk和cglib(上)
158 0
Java动态代理模式jdk和cglib(上)
|
Java
Java动态代理之JDK实现和CGlib实现(简单易懂)
转自:https://www.cnblogs.com/ygj0930/p/6542259.html
951 0
|
Java 设计模式
JAVA动态代理(JDK版本)
在代理设计模式里,代理类扮演桥接使用方和实现方的角色。使用者通过代理类获得实现类的访问权限,并通过代理类定制执行业务逻辑前、后的处理流程。
2316 0