【java基础】闲话 CGLib(不烧脑版)

引子

CGLib 是 Code Generation Library 的简称,是一个基于字节码技术(ASM)的代码生成库。与上一篇介绍的 JDK 动态代理的显著不同是,CGLib 可以生成指定类的子类,而无需该类实现接口。

从前面我们介绍的 ClassLoader 部分我们知道,JVM 运行时通过 ClassLoader 读取 class 文件来加载类对象( class 文件存储的便是提前编译好的字节码)。而 CGLib 就是在运行时动态生成字节码,然后通过 ClassLoader 加载为类对象。CGLib 本身并没有实现生成字节码逻辑,而是基于 java 的 ASM 包还实现的,而 ASM 比较复杂,所以我们就不在生成字节码上展开说明了,感兴趣的朋友可以自己搜索相关资料学习,本文的重点还是放在CGLib 是如何实现动态代理的方面着重展开。

例子

我们还是从一个简单的例子开始逐步的学习,我们实现一个在 UserService.add 方法调用的前后打印日志的功能。

//要被增强的类
public class UserService {
    public void add(String name) {
        System.out.println("保存用户:" + name);
    }
}

//方法拦截
public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        /**
        * Object o : 生成的代理对象
        * Method method : 父类对应的 Method.这里就是 UserService的 add
        * Object[] objects : 方法参数
        * MethodProxy methodProxy : 代理方法(区别于method参数)
        **/
        System.out.println("方法执行前打印");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("方法执行后打印");
        return result;
    }
}

//测试类
public class Main {
    public static void main(String[] args) {
        //这句话是为了将生成的class 文件保存下来,用于分析
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/chenxing/code/xchen/demo-maven/gen");
        //创建增强
        Enhancer enhancer = new Enhancer();
        //指定父类,也就是我们要增强的类
        enhancer.setSuperclass(UserService.class);
        //指定增强的逻辑
        enhancer.setCallback(new LogInterceptor());
        //创建代理对象
        UserService o = (UserService) enhancer.create();
        //调用代理对象
        o.add("小明");
    }
}

输出:

方法执行前打印
保存用户:小明
方法执行后打印

我们看到,代理的最核心的逻辑在LogInterceptor里面,与 上一篇我们讲到的JDK 动态代理类似,也是在调用方法的前后写入自定义的逻辑实现功能增强,而不同的是,这里我们没有直接调用 method的 invoke 方法,而是通过 methodProxy 对象的 invokeSuper 来实现方法调用。那整个过程具体是怎么调用的呢?
第一步:生成代理对象

UserService o = (UserService) enhancer.create();

第二步:调用代理对象的 add 方法:这个代理对象内封装了我们实现的 LogInterceptor 对象,然后在调用代理对象的 add 方法内调用LogInterceptor的intercept方法。

o.add("小明"); //内部调用了LogInterceptor的intercept方法

第三步:调用原始方法:就是上面说到的通过 methodProxy.invokeSuper 来调用原始的方法了。这里比较复杂,现在只要知道是为了调用原始的方法即可,后面会介绍到。

分析

直接上生成的源码!!!算了,那玩意变量名有 1 米长,看着就头疼,我来把代码翻译的可读一些,去掉里面不需要关注的部分,简化一下给大家看,当然,大家也可以直接参考上面的内容自己生成实际的代码查看。

我们先来看一下生成的子类:(这里是我简化重写的,表示那个意思)

public class UserServiceChild extends UserService {
    private MethodInterceptor methodInterceptor;
    private static final Method addMethod;
    private static final MethodProxy addMethodProxy;
    //省略以上参数的初始化内容
    
    final void add0(String var1) {
        super.add(var1);
    }
    public final void add(String var1) {
        methodInterceptor.intercept(this, addMethod, new Object[]{var1}, addMethodProxy);
    }
}

然后我们看到这里调用了 LogInterceptor 的 intercept方法,而通过 LogInterceptor 中我们又看到其调用了MethodProxy.invokeSuper()方法。如下:

class FastClassInfo {
  private FastClass fastClass;
  //表示当前 MethodProxy对象表示的方法在FastClass中的索引值,往下看就明白了
  private int methodIndex; 
}

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        FastClassInfo fci = initFastClass();
        //这里的 methodIndex 是 1
        return fci.fastClass.invoke(fci.methodIndex, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

这里有一个FastClassInfo,这个是干嘛的?其实这个是 CGLib 为了解决反射慢的问题生成的一个加速类,通过把类的方法进行编码,比如 0 代表 add方法,1代表 add0 方法,来快速调用对应的方法,而不需要通过反射,如下:

public class UserServiceChildFastClass extends FastClass {
    @Override
    public Object invoke(int methodIndex, Object proxyObj, Object[] methodArgs) throws InvocationTargetException {
        UserServiceChild proxy = (UserServiceChild) proxyObj;
        try {
            switch (methodIndex) {
                case 0:
                    proxy.add((String)methodArgs[0]);
                    return null;
                case 1:
                    proxy.add0((String)methodArgs[0]);
                    return null;
            }
        } catch (Throwable e) {
            throw new InvocationTargetException(e);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
}

这里我们因为调用时methodIndex=1(initFastClass()方法时根据当前的方法签名从 UserServiceChildFastClass 中获得,这里给出的UserServiceChildFastClass并非完整版,只是示意,有兴趣可以自己运行一下代码生成一下),所以调用的是UserServiceChild的 add0 方法,而 add0 方法又是调用的super.add() 就是 UserService.add 方法。

最后,MethodProxy 对象其实还有一个 invoke 方法,如果这么写会造成 StackOverflow.:

public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        /**
        * Object o : 生成的代理对象
        * Method method : 父类对应的 Method.这里就是 UserService的 add
        * Object[] objects : 方法参数
        * MethodProxy methodProxy : 代理方法(区别于method参数)
        **/
        System.out.println("方法执行前打印");
        Object result = methodProxy.invoke(o, objects);
        System.out.println("方法执行后打印");
        return result;
    }
}

这是为啥呢?其实是因为 invoke 方法直接调用了代理对象的 add 方法。而从上面可知add又调用了 Intercept方法,Intercept方法又调用了invoke 方法,无限循环!最后就 StackOverflow 了。

结束~~~,写的不好,轻喷!

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个使用CGLIB进行动态代理的简单例子: 假设我们有一个接口UserService,其中定义了一个方法getUserById(int id),我们希望对该方法进行拦截,记录方法的执行时间。我们可以使用CGLIB来生成代理类,并通过MethodInterceptor来实现拦截逻辑。 首先,我们需要引入CGLIB的相关依赖: ```xml <!-- CGLIB --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> ``` 然后,定义一个UserService的实现类UserServiceImpl: ```java public class UserServiceImpl implements UserService { @Override public User getUserById(int id) { // 模拟查询数据库 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return new User(id, "张三"); } } ``` 接下来,我们定义一个MethodInterceptor来实现拦截逻辑: ```java public class TimeInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { long start = System.currentTimeMillis(); Object result = proxy.invokeSuper(obj, args); long end = System.currentTimeMillis(); System.out.println("方法" + method.getName() + "执行时间:" + (end - start) + "ms"); return result; } } ``` 最后,我们使用CGLIB生成代理类,将拦截逻辑加入到代理类中: ```java public class CglibTest { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(userService.getClass()); enhancer.setCallback(new TimeInterceptor()); UserService proxy = (UserService) enhancer.create(); proxy.getUserById(1); } } ``` 在上面的代码中,我们使用Enhancer类来生成代理类,将原始的UserService实现类作为代理类的父类,然后设置MethodInterceptor作为代理类的回调函数。最后,我们通过enhancer.create()方法来创建代理类,并将其强制转换为UserService接口类型,从而实现代理类的功能。 当我们调用代理类的getUserById方法时,MethodInterceptor中的intercept方法将被执行,从而实现了对方法的拦截。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值