cglib测试例子和源码详解

目录

  • 简介

    • 为什么会有动态代理?
    • 常见的动态代理有哪些?
    • 什么是cglib
  • 使用例子
    • 需求
    • 工程环境
    • 主要步骤
    • 创建项目
    • 引入依赖
    • 编写被代理类
    • 编写MethodInterceptor接口实现类
    • 编写测试类
    • 运行结果
  • 源码分析-获得代理类的过程
    • 主要步骤
    • 获得key
    • 利用key从缓存中获取Class
    • 生成代理类Class
  • 代理类代码分析
    • cglib生成文件
    • 代理类源码
    • MethodProxy.create
    • MethodProxy.invokeSuper
    • FastClass.invoke

简介

为什么会有动态代理?

举个例子,当前有一个用户操作类,要求每个方法执行前打印访问日志。

这里可以采用两种方式:

第一种,静态代理。即通过继承原有类来对方法进行扩展。

当然,这种方式可以实现需求,但是当类的方法很多时,我们需要逐个添加打印日志的代码,非常繁琐。此时,如果要求加入权限校验,这个时候又需要再创建一个代理类。

第二种,动态代理。即通过拦截器的方式来对方法进行扩展。

动态代理只需要重写拦截器的一个方法,相比静态代理,可以减少很多代码。而且,动态代理要实现不同的代理类,只要选择不同的拦截器就可以了(可以选择多个),代理类不需要我们自己实现,可以有效实现代码解耦和可重用。

不限于以上优点,动态代理被广泛应用于日志记录、性能统计、安全控制、事务处理、异常处理等等,是spring实现AOP的重要支持。

常见的动态代理有哪些?

常用的动态代理有:JDK动态代理、cglib。

感兴趣的可以研究下aspectJ

什么是cglib

cglib基于asm字节码生成框架,用于动态生成代理类。与JDK动态代理不同,有以下几点不同:

  1. JDK动态代理要求被代理类实现某个接口,而cglib无该要求。
  2. JDK动态代理生成的代理类是该接口实现类,也就是说,不能代理接口中没有的方法,而cglib生成的代理类继承被代理类。
  3. 在字节码的生成和类的创建上,JDK的动态代理效率更高。
  4. 在代理方法的执行效率上,由于采用了FastClass,cglib的效率更高(以空间换时间)。

注:因为JDK动态代理中代理类中的方法是通过反射调用的,而cglib因为引入了FastClass,可以直接调用代理类对象的方法。

使用例子

需求

模拟对用户数据进行增删改前打印访问日志

工程环境

JDK:1.8

maven:3.6.1

IDE:STS4

主要步骤

  1. 创建Enhancer对象:Enhancer是cglib代理的对外接口,以下操作都是调用这个类的方法
  2. setSuperclass(Class superclass):代理谁?
  3. setCallback(final Callback callback):怎么代理?(我们需要实现Callback的子接口MethodInterceptor,重写其中的intercept方法,该方法定义了代理规则)
  4. create():获得代理类
  5. 使用代理类

创建项目

项目类型Maven Project,打包方式jar

引入依赖

    <!-- cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
    </dependency>
    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

编写被代理类

包路径:cn.zzs.cglib
这里只是简单地测试,不再引入复杂的业务逻辑。

public class UserController {
    public void save() {
        System.out.println("增加用户");
    }
    public void delete() {
        System.out.println("删除用户");
    }
    public void update() {
        System.out.println("修改用户");
    }
    public void find() {
        System.out.println("查找用户");
    }
}

编写MethodInterceptor接口实现类

包路径:cn.zzs.cglib

public class LogInterceptor implements MethodInterceptor {

    @Override
    public Object intercept( Object obj, Method method, Object[] args, MethodProxy proxy ) throws Throwable {
        // 设置需要代理拦截的方法
        HashSet<String> set = new HashSet<String>( 6 );
        set.add( "save" );
        set.add( "delete" );
        set.add( "update" );
        // 进行日志记录
        if( method != null && set.contains( method.getName() ) ) {
            System.out.println( "进行" + method.getName() + "的日志记录" );
        }
        // 执行被代理类的方法
        Object obj2 = proxy.invokeSuper( obj, args );
        return obj2;
    }
}

编写测试类

这里的输出代理类的class文件,方便后面分析。

包路径:test下的cn.zzs.cglib

public class CglibTest {
    @Test
    public void test01() {
        // 设置输出代理类到指定路径
        System.setProperty( DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/growUp/test" );
        // 创建Enhancer对象,用于生成代理类
        Enhancer enhancer = new Enhancer();
        // 设置哪个类需要代理
        enhancer.setSuperclass( UserController.class );
        // 设置怎么代理,这里传入的是Callback对象-MethodInterceptor父类
        LogInterceptor logInterceptor = new LogInterceptor();
        enhancer.setCallback( logInterceptor );
        // 获取代理类实例
        UserController userController = ( UserController )enhancer.create();
        // 测试代理类
        System.out.println( "-------------" );
        userController.save();
        System.out.println( "-------------" );
        userController.delete();
        System.out.println( "-------------" );
        userController.update();
        System.out.println( "-------------" );
        userController.find();
    }
}

运行结果

CGLIB debugging enabled, writing to 'D:/growUp/test'
-------------
进行save的日志记录
增加用户
-------------
进行delete的日志记录
删除用户
-------------
进行update的日志记录
修改用户
-------------
查找用户

源码分析-获得代理类的过程

主要步骤

这里先简单说下过程:

  1. 根据当前Enhancer实例生成一个唯一标识key
  2. 用key去缓存中找代理类的Class实例
  3. 找到了就返回代理类实例
  4. 找不到就生成后放入map,再返回代理类实例

获得key

接下来具体介绍下。

首先,一进来就先调用了createHelper()

    public Object create() {
        classOnly = false;
        argumentTypes = null;
        return createHelper();
    }

createHelper()中,创建了key,这个用于唯一标识当前类及相关配置,用于在缓存中存取代理类的Class实例。接着调用父类AbstractClassGeneratorcreate(Object key)方法获取代理类实例。

    private Object createHelper() {
        preValidate();
        Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
                ReflectUtils.getNames(interfaces),
                filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
                callbackTypes,
                useFactory,
                interceptDuringConstruction,
                serialVersionUID);
        this.currentKey = key;
        //获取代理类实例
        Object result = super.create(key);
        return result;
    }

create(Object key)中,会调用内部类ClassLoaderDataget(AbstractClassGenerator gen, boolean useCache)方法获取代理类的Class实例。

    protected Object create(Object key) {
        try {
            ClassLoader loader = getClassLoader();
            Map<ClassLoader, ClassLoaderData> cache = CACHE;
            ClassLoaderData data = cache.get(loader);
            if (data == null) {
                synchronized (AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                        data = new ClassLoaderData(loader);
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }
            this.key = key;
            //获取代理类的Class类实例
            Object obj = data.get(this, getUseCache());
            //获取代理类实例
            if (obj instanceof Class) {
                return firstInstance((Class) obj);
            }
            return nextInstance(obj);
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new CodeGenerationException(e);
        }
    }

利用key从缓存中获取Class

ClassLoaderData有一个重要字段generatedClasses,是一个LoadingCache缓存对象,存放着当前类加载器加载的代理类的Class类实例。在以下方法中,就是从它里面寻找,通过key匹配查找。

        //这个对象存放着当前类加载器加载的代理类的Class类实例
        private final LoadingCache<AbstractClassGenerator, Object, Object> generatedClasses;
        public Object get(AbstractClassGenerator gen, boolean useCache) {
            if (!useCache) {
              return gen.generate(ClassLoaderData.this);
            } else {
              //获取代理类的Class类实例
              Object cachedValue = generatedClasses.get(gen);
              return gen.unwrapCachedValue(cachedValue);
            }
        }

下面重点看下LoadingCache这个类,需要重点理解三个字段的意思:

//K:AbstractClassGenerator 这里指Enhancer类
//KK:Object 这里指前面生成key的类
//V:Object 这里指代理类的Class类
public class LoadingCache<K, KK, V> {
    //通过key可以拿到代理类的Class实例
    protected final ConcurrentMap<KK, Object> map;
    //通过loader.apply(Enhancer实例)可以获得代理类的Class实例
    protected final Function<K, V> loader;
    //通过keyMapper.apply(Enhancer实例)可以获得key
    protected final Function<K, KK> keyMapper;
    ·······
}

这里通过key去map里找代理类的Class实例,如果找不到,会重新生成后放入map中。

    public V get(K key) {
        final KK cacheKey = keyMapper.apply(key);
        Object v = map.get(cacheKey);
        if (v != null && !(v instanceof FutureTask)) {
            return (V) v;
        }

        return createEntry(key, cacheKey, v);
    }

生成代理类Class

以上基本说完如何从缓存中拿到代理类实例的方法,接下来简单看下生成代理类的过程,即loader.apply(Enhancer实例),里面的generate会生成所需的Class对象,比较复杂,后面有时间再研究吧。

    public Object apply(AbstractClassGenerator gen) {
        Class klass = gen.generate(ClassLoaderData.this);
        return gen.wrapCachedClass(klass);
    }

代理类代码分析

cglib生成文件

在一开始指定的路径下,可以看到生成了三个文件,前面简介里说到在代理类的生成上,cglib的效率低于JDK动态代理,主要原因在于多生成了两个FastClass文件,至于这两个文件有什么用呢?接下来会重点分析:

代理类源码

本文采用Luyten作为反编译工具,一开始用jd-gui解析,但错误太多。

下面看看代理类的源码。

在初始化时,代理类的字段都会被初始化,这里涉及到MethodProxycreate方法。

在实际调用update方法是会调用MethodInterceptor对象的intercept方法,执行我们自定义的代码后,最终会调用的是MethodProxyinvokeSuper方法。下面重点看看这些方法。

注:考虑篇幅问题,这里仅展示update方法。

//生成类的名字规则是:被代理classname + "$$"+classgeneratorname+"ByCGLIB"+"$$"+key的hashcode
public class UserController$$EnhancerByCGLIB$$e6f193aa extends UserController 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;

    //我们一开始传入的MethodInterceptor对象
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    //被代理类update方法
    private static final Method CGLIB$update$0$Method;
    //代理类update方法
    private static final MethodProxy CGLIB$update$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        //代理类Class对象
        final Class<?> forName = Class.forName("cn.zzs.cglib.UserController$$EnhancerByCGLIB$$e6f193aa");
        //被代理类Class对象
        final Class<?> forName2;
        final Method[] methods = ReflectUtils.findMethods(new String[] { "update", "()V", "find", "()V", "delete", "()V", "save", "()V" },
                (forName2 = Class.forName("cn.zzs.cglib.UserController")).getDeclaredMethods());
        //初始化被代理类update方法
        CGLIB$update$0$Method = methods[0];
        //初始化代理类update方法
        CGLIB$update$0$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()V", "update", "CGLIB$update$0");
    }

    final void CGLIB$update$0() {
        super.update();
    }

    public final void update() {
        MethodInterceptor cglib$CALLBACK_2;
        MethodInterceptor cglib$CALLBACK_0;
        if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
            CGLIB$BIND_CALLBACKS(this);
            cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
        }
        //一般走这里,即调用我们传入MethodInterceptor对象的intercept方法
        if (cglib$CALLBACK_0 != null) {
            cglib$CALLBACK_2.intercept((Object)this, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Method, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$emptyArgs, UserController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Proxy);
            return;
        }
        super.update();
    }

MethodProxy.create

通过以下代码可以知道,MethodProxy对象CGLIB$update$0$Proxy持有了代理类和被代理类的Class实例,以及代理方法和被代理方法的符号表示,这两个sig用于后面获取方法索引。

    //Class c1, 被代理对象
    //Class c2, 代理对象
    //String desc, 参数列表描述
    //String name1, 被代理方法
    //String name2,代理方法
    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        //创建方法签名
        proxy.sig1 = new Signature(name1, desc);
        proxy.sig2 = new Signature(name2, desc);
        //创建createInfo
        proxy.createInfo = new CreateInfo(c1, c2);
        return proxy;
    }

MethodProxy.invokeSuper

以下方法中会去创建两个FastClass文件,也就是我们看到的另外两个文件。当然,它们只会创建一次。
另外,通过原来的方法签名获得了update的方法索引。

    //传入参数obj:代理类实例
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            //初始化,创建了两个FastClass类对象,并根据原来的方法签名得到方法索引
            init();
            //这个对象持有两个FastClass类对象和方法的索引
            FastClassInfo fci = fastClassInfo;
            //调用了代理对象FastClass的invoke方法
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }
    private void init(){
        if (fastClassInfo == null){
            synchronized (initLock){
                if (fastClassInfo == null){
                    CreateInfo ci = createInfo;
                    FastClassInfo fci = new FastClassInfo();
                    //helper方法用ASM框架去生成了两个FastClass类
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(sig1);
                    fci.i2 = fci.f2.getIndex(sig2);
                    fastClassInfo = fci;
                    createInfo = null;
                }
            }
        }
    }
private static class FastClassInfo{
    FastClass f1;//被代理对象FastClass
    FastClass f2;//代理对象FastClass
    int i1;//被代理update方法的索引
    int i2; //代理update方法的索引
}  

FastClass.invoke

根据方法索引进行匹配,可以直接调用代理类实例的方法,而不需要像JDK动态代理一样采用反射的方式,所以在方法执行上,cglib的效率会更高。

    //传入参数:
    //n:方法索引
    //o:代理类实例
    //array:方法输入参数
    public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
        final UserController$$EnhancerByCGLIB$$e6f193aa userController$$EnhancerByCGLIB$$e6f193aa = (UserController$$EnhancerByCGLIB$$e6f193aa)o;
        try {
            switch (n) {
                case 0: {
                    return new Boolean(userController$$EnhancerByCGLIB$$e6f193aa.equals(array[0]));
                }
                case 1: {
                    return userController$$EnhancerByCGLIB$$e6f193aa.toString();
                }
                case 2: {
                    return new Integer(userController$$EnhancerByCGLIB$$e6f193aa.hashCode());
                }
                case 3: {
                    return userController$$EnhancerByCGLIB$$e6f193aa.clone();
                }
                case 4: {
                    //通过匹配方法索引,直接调用该方法
                    userController$$EnhancerByCGLIB$$e6f193aa.update();
                    return null;
                }
                ·······

        }
        catch (Throwable t) {
            throw new InvocationTargetException(t);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

版权所有,相关源码请移步:https://github.com/ZhangZiSheng001/cglib-demo.git

原文地址:https://www.cnblogs.com/ZhangZiSheng001/p/11917086.html

时间: 2024-11-06 03:31:08

cglib测试例子和源码详解的相关文章

DBCP2的使用例子和源码详解(包括JNDI和JTA支持的使用)

目录 简介 使用例子 需求 工程环境 主要步骤 创建项目 引入依赖 编写jdbc.prperties 获取连接池和获取连接 编写测试类 配置文件详解 数据库连接参数 连接池数据基本参数 连接检查参数 缓存语句 事务相关参数 连接泄漏回收参数 其他 源码分析 数据源创建 BasicDataSource.getConnection() BasicDataSource.createDataSource() 获取连接对象 PoolingDataSource.getConnection() Generic

CacheManager彻底解密:CacheManager运行原理流程图和源码详解(DT大数据梦工厂)

内容: 1.CacheManager重大价值: 2.CacheManager运行原理图: 3.CacheManager源码解析: BlockManager针对Cache这样的行为做了CacheManager Spark出色的原因: 1.Spark基于RDD构成了一体化.多元化的大数据处理中心(不需要再处理多种范式来部署多种框架,只要Spark!!!降低成本投入获得更高的产出): 2.迭代,因为在计算的时候迭代,在构建复杂算法的时候非常方便(图计算.机器学习.数据仓库),而CacheManager

spring查看生成的cglib代理类源码详解

1.让程序阻塞(抛出异常会导致程序结束,所以在抛出异常之前阻塞) 2. windows控制台 cd到jdk目录下的lib目录,找到sa-jdi.jar 执行: java -classpath sa-jdi.jar "sun.jvm.hotspot.HSDB" 出现如下窗口: 点File—>Attach to hotspot proccess 再运行cmd 执行 jps -l 列出java进程 找到项目进程: 输入进程id后 Tools—>Class  Browser 点进去

深入Java基础(四)--哈希表(1)HashMap应用及源码详解

继续深入Java基础系列.今天是研究下哈希表,毕竟我们很多应用层的查找存储框架都是哈希作为它的根数据结构进行封装的嘛. 本系列: (1)深入Java基础(一)--基本数据类型及其包装类 (2)深入Java基础(二)--字符串家族 (3)深入Java基础(三)–集合(1)集合父类以及父接口源码及理解 (4)深入Java基础(三)–集合(2)ArrayList和其继承树源码解析以及其注意事项 文章结构:(1)哈希概述及HashMap应用:(2)HashMap源码分析:(3)再次总结关键点 一.哈希概

Shiro 登录认证源码详解

Shiro 登录认证源码详解 Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证.授权管理.企业级会话管理和加密等功能,相比 Spring Security 来说要更加的简单. 本文主要介绍 Shiro 的登录认证(Authentication)功能,主要从 Shiro 设计的角度去看这个登录认证的过程. 一.Shiro 总览 首先,我们思考整个认证过程的业务逻辑: 获取用户输入的用户名,密码: 从服务器数据源中获取相应的用户名和密码: 判断密码是否匹配,决定是否

Android编程之Fragment动画加载方法源码详解

上次谈到了Fragment动画加载的异常问题,今天再聊聊它的动画加载loadAnimation的实现源代码: Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { 接下来具体看一下里面的源码部分,我将一部分一部分的讲解,首先是: Animation animObj = fragment.onCreateAnimation(transit, enter, fragm

HTTP协议状态码详解(HTTP Status Code)(转)

原文链接:HTTP协议状态码详解(HTTP Status Code) 使用ASP.NET/PHP/JSP 或者javascript都会用到http的不同状态,一些常见的状态码为: 200 – 服务器成功返回网页 404 – 请求的网页不存在 503 – 服务不可用 1xx(临时响应) 表示临时响应并需要请求者继续执行操作的状态代码. 代码   说明 100   (继续) 请求者应当继续提出请求. 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分.  101   (切换协议) 请求者已要

Spring IOC源码详解之容器初始化

Spring IOC源码详解之容器初始化 上篇介绍了Spring IOC的大致体系类图,先来看一段简短的代码,使用IOC比较典型的代码 ClassPathResource res = new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDe

butterknife源码详解

butterknife源码详解 作为Android开发者,大家肯定都知道大名鼎鼎的butterknife.它大大的提高了开发效率,虽然在很早之前就开始使用它了,但是只知道是通过注解的方式实现的,却一直没有仔细的学习下大牛的代码.最近在学习运行时注解,决定今天来系统的分析下butterknife的实现原理. 如果你之前不了解Annotation,那强烈建议你先看注解使用. 废多看图: 从图中可以很直观的看出它的module结构,以及使用示例代码. 它的目录和我们在注解使用这篇文章中介绍的一样,大体