cglib动态代理实现原理

1,077 阅读1分钟

cglib的动态代理不但可以代理接口,还可以代理类

打开cglib的依赖可以发现其依赖了asm框架来实现动态代理

image.png

我们先使用cglib的动态代理来代理实现打印调用方法的耗时。

先创建类UserService,实现登录逻辑

package asm;

public class UserService {
    public boolean login(String username, String password) {
        return "admin".equals(username) && "admin".equals(password);
    }
}

实现MethodInterceptor回调类型,打印耗时。

package asm;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class UserMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return proxy.invokeSuper(obj, args);
        } catch (Exception e) {
            throw e;
        } finally {
            System.out.println("invoke-" + obj.getClass().getName() + "." + method.getName() + ":" + (System.currentTimeMillis() - start) + "ms");
        }
    }
}

然后使用Enhancer创建代理类,使用cglib非常方便:

  1. 创建Enhancer实例
  2. 设置需要代理的基类
  3. 设置回调类型
package asm;

import net.sf.cglib.proxy.Enhancer;

public class Main {
    public static void main(String[] args) {
        System.out.println("cglib动态代理开始");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new UserMethodInterceptor());
        UserService userService = (UserService) enhancer.create();
        System.out.println(userService.login("admin", "admin"));
        System.out.println(userService.login("admin", "admin1"));
    }
}

打印结果:

image.png

接下来探索一下动态代理生成的类到底长什么样

打开DebuggingClassWriter类,可以发现输出class文件的目录是读取系统配置DEBUG_LOCATION_PROPERTY的值

image.png

那先设置DEBUG_LOCATION_PROPERTY的值为我们方便打开的文件目录,这里我设置为'./cglib'

image.png

然后执行main方法,发现生成了三个class文件,看控制台输出,我们调用的类是后缀为 $$6a598181的类

image.png

接下来看看生成的类UserServiceEnhancerByCGLIBEnhancerByCGLIB6a598181

image.png

  • 继承了我们设定的父类UserService
  • 实现了接口Factory

先看一下接口Factory

Factory接口描述

  • Enhancer类返回的所有增强实例都实现了这个接口。
  • 将此接口用于新实例比通过Enhancer接口或使用反射更快。
  • 此外,要拦截对象构造期间调用的方法,您必须使用这些方法而不是反射。

Factory的方法定义
提供了获取实例对象和添加回调

  • 无参构造
  • 有参构造
  • 设置回调类型
  • 获取回调类型

public interface Factory {
    /**
     * 使用无参数构造函数创建相同类型的新实例。 该对象的类必须是使用单一回调类型创建的。 如果需要多个回调,则会抛出异常。
     * 参数:
     * 回调 - 要使用的新拦截器
     * 返回:相同类型的新实例
     */     
    Object newInstance(Callback callback);
    
    /**
     * 使用无参数构造函数创建相同类型的新实例。
     * 参数:
     * callbacks – 要使用的新回调
     * 返回:相同类型的新实例
     */     
    Object newInstance(Callback[] callbacks);

    /**
     * 使用与给定签名匹配的构造函数创建相同类型的新实例。
     * 参数:
     * types - 构造函数参数类型
     * args – 构造函数参数
     * callbacks – 要使用的新拦截器
     * 返回:相同类型的新实例
     */
    Object newInstance(Class[] types, Object[] args, Callback[] callbacks);

    /**
     * 返回指定索引处的Callback实现
     * 参数:index – 回调索引
     * 返回:回调实现
     */
    Callback getCallback(int index);

    /**
     * Set the callback for this object for the given type.
     * 参数:
     * index – 要替换的回调索引
     * callback – 新的回调
     */
    void setCallback(int index, Callback callback);

    /**
     * 一次替换此对象的所有回调
     * 参数:
     * callbacks – 要使用的新回调
     */
    void setCallbacks(Callback[] callbacks);

    /**
     * 获取 this 对象的当前回调集。
     * 返回:一个新的数组实例
     */     
    Callback[] getCallbacks();
}

看完Factory,再回过头来看类UserServiceEnhancerByCGLIBEnhancerByCGLIB6a598181

image.png

image.png 可以看到生成的类中,方法CGLIBloginlogin0调用的就是父类UserService的login方法,而login方法里面会调用回调方法intercept进行代理逻辑处理。

调用intercept方法的入参:

  • obj: 生成的代理类对象UserServiceEnhancerByCGLIBEnhancerByCGLIB6a598181
  • method:UserService.login方法
  • args:方法调用参数
  • proxy:封装CGLIBloginlogin0方法的代理