User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Java   ›   正文
Java

Java—Cglib基本使用

2021-07-05 16:35:14
894  0 1
参考目录 隐藏
1) Cglib是什么
2) 依赖
3) Enhancer 介绍
4) Callback
5) Callback
6) MethodInterceptor:
7) InvocationHandler:
8) NoOp:
9) LazyLoader:
10) Dispatcher:
11) ImmutableBean 不可变Bean
12) BeanGenerator
13) BeanCopier
14) BeanMap
15) 使用Cglib代码对类做代理
16) 使用Cglib定义不同的拦截策略
17) 构造函数不拦截方法
18) 为什么 Kotlin 使用 cglib 的 Enhancer 动态代理不生效

阅读完需:约 13 分钟

Cglib是什么

Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截。

对此图总结一下:

  • 最底层的是字节码Bytecode,字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式,例如iload_0、iconst_1、if_icmpne、dup等
  • 位于字节码之上的是ASM,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉
  • 位于ASM之上的是CGLIB、Groovy、BeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码—-只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码
  • 位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了,这一层大家都比较熟悉
  • 最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序

依赖

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.12</version>
        </dependency>
  • CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。 其被广泛应用于AOP框架(Spring)中,用以提供方法拦截操作。
  • CGLIB代理主要通过对字节码的操作,以控制对象的访问。
  • CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。
  • CGLIB相比于JDK动态代理更加强大:
    • JDK动态代理虽然简单易用,但只能对接口进行代理。
    • 如果要代理的类为一个普通类,没有接口,那么Java动态代理就没法使用了。
  • Java动态代理使用Java原生的反射API进行操作(运行期),在生成类上比较高效。
  • CGLIB使用ASM框架直接对字节码进行操作(编译期),在类的执行过程中比较高效

Enhancer 介绍

  • Enhancer :
    • Enhancer既能够代理普通的class,也能够代理接口。
    • Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。
    • Enhancer不能够拦截final类与方法。

用来设置父类型

  • Enhancer.setSuperclass(Class superclass);

增强

  • Enhancer.setCallback(Callback callback);
  • Enhancer.setCallback(new InvocationHandler(){});
  • Enhancer.setCallback(new MethodInterceptor(){});

方法是用来创建代理对象,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。

  • Enhancer.create(Class type, Callback callback);
  • Enhancer.create(Class superclass, Class[] interfaces, Callback callback);
  • Enhancer.create(Class[] argumentTypes, Object[] arguments);

Callback

Callback

是一个空的接口,在Cglib中它的实现类有以下几种:

  • MethodInterceptor
  • NoOp
  • LazyLoader
  • Dispatcher
  • InvocationHandler
  • FixedValue

MethodInterceptor:

它可以实现类似于AOP编程中的环绕增强(around-advice)。

它只有一个方法:
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy)

代理类的所有方法调用都会转而执行这个接口中的intercept方法而不是原方法。 如果需要在intercept方法中执行原方法可以使用参数method进行反射调用, 或者使用参数proxy 一 proxy.invokeSuper(obj, args); 后者会快一些(反射调用比正常的方法调用的速度慢很多)。

MethodInterceptor允许我们完全控制被拦截的方法,并且提供了手段对原方法进行调用,因为 MethodInterceptor的效率不高,它需要产生不同类型的字节码, 并且需要生成一些运行时对象(InvocationHandler就不需要),所以Cglib提供了其它的接口供我们选择。

InvocationHandler:

它的使用方式和MethodInterceptor差不多。

需要注意的一点是,所有对invoke()方法的参数proxy对象的方法调用都会被委托给同一个InvocationHandler,所以可能会导致无限循环。

NoOp:

这个接口只是简单地把方法调用委托给了被代理类的原方法,不做任何其它的操作。

LazyLoader:

它也提供了一个方法:Object loadObject()

loadObject()方法会在第一次被代理类的方法调用时触发,它返回一个代理类的对象。

这个对象会被存储起来然后负责所有被代理类方法的调用,一种lazy模式。

如果被代理类或者代理类的对象的创建比较麻烦,而且不确定它是否会被使用,那么可以选择使用这种lazy模式来延迟生成代理。

Dispatcher:

Dispatcher和LazyLoader接口相同,也是提供了loadObject()方法。

不过它们之间不同的地方在于,Dispatcher的loadObject()方法在每次发生对原方法的调用时都会被调用并返回一个代理对象来调用原方法。也就是说Dispatcher的loadObject()方法返回的对象并不会被存储起来,可以类比成Spring中的Prototype类型,而LazyLoader则是lazy模式的Singleton。


ImmutableBean 不可变Bean

ImmutableBean允许创建一个原来对象的包装类,
这个包装类是不可变的,任何改变底层对象的包装类操作都会抛出IllegalStateException。

    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");

    SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean); //创建不可变类

    immutableBean.setValue("Hello cglib");  //直接修改将throw exception

    bean.setValue("Hello world, again");    //可以通过底层对象来进行修改

BeanGenerator

运行时动态的创建一个bean

    BeanGenerator beanGenerator = new BeanGenerator();
    beanGenerator.addProperty("value",String.class);
    Object myBean = beanGenerator.create();

    Method setter = myBean.getClass().getMethod("setValue",String.class);
    setter.invoke(myBean,"Hello cglib");

    Method getter = myBean.getClass().getMethod("getValue");

BeanCopier

从一个bean复制到另一个bean中,还提供了一个转换器,用来在转换的时候对bean的属性进行操作。

BeanCopier.create(Class source, Class target, boolean useConverter)

    BeanCopier copier = BeanCopier.create(Bean1.class, Bean2.class, false);  //设置为true,则使用converter
    
    Bean1 bean1 = new Bean1();
    bean1.setValue("Hello cglib");
    Bean2 bean2 = new Bean2();
    copier.copy(bean1, bean2, null);    //设置为true,则传入converter,指明怎么进行转换

BeanMap

BeanMap类实现了Java Map,将一个bean对象中的所有属性转换为一个<String,Obejct>的Java Map

    BeanMap map = BeanMap.create(bean); //将对象转为BeanMap

public abstract class BeanMap implements Map {

    protected Object bean;

    public Object get(Object key) {
        return this.get(this.bean, key);
    }

    public Object put(Object key, Object value) {
        return this.put(this.bean, key, value);
    }

    public void setBean(Object bean) {
        this.bean = bean;
    }

    public Object getBean() {
        return this.bean;
    }
}

使用Cglib代码对类做代理

Cglib代码示例—-对类做代理。

首先定义一个Dao类,里面有一个select()方法和一个update()方法:

public class Dao {
    
    public void update() {
        System.out.println("PeopleDao.update()");
    }
    
    public void select() {
        System.out.println("PeopleDao.select()");
    }
}

创建一个Dao代理,实现MethodInterceptor接口,目标是在update()方法与select()方法调用前后输出两句话:

public class DaoProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        System.out.println("Before Method Invoke");
        proxy.invokeSuper(object, objects);
        System.out.println("After Method Invoke");
        
        return object;
    }
    
}

intercept方法的参数名并不是原生的参数名,我做了自己的调整,几个参数的含义为:

  • Object表示要进行增强的对象
  • Method表示拦截的方法
  • Object[]数组表示参数列表,基本数据类型需要传入其包装类型,如int–>Integer、long-Long、double–>Double
  • MethodProxy表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用

写一个测试类:

public class CglibTest {

    @Test
    public void testCglib() {
        DaoProxy daoProxy = new DaoProxy();
        
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);
        enhancer.setCallback(daoProxy);
        
        Dao dao = (Dao)enhancer.create();
        dao.update();
        dao.select();
    }
    
}

这是使用Cglib的通用写法,setSuperclass表示设置要代理的类,setCallback表示设置回调即MethodInterceptor的实现类,使用create()方法生成一个代理对象,注意要强转一下,因为返回的是Object。最后看一下运行结果:

Before Method Invoke
PeopleDao.update()
After Method Invoke
Before Method Invoke
PeopleDao.select()
After Method Invoke

使用Cglib定义不同的拦截策略

再扩展一点,比方说在AOP中我们经常碰到的一种复杂场景是:我们想对类A的B方法使用一种拦截策略、类A的C方法使用另外一种拦截策略。

在本例中,即我们想对select()方法与update()方法使用不同的拦截策略,那么我们先定义一个新的Proxy:

public class DaoAnotherProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        
        System.out.println("StartTime=[" + System.currentTimeMillis() + "]");
        method.invokeSuper(object, objects);
        System.out.println("EndTime=[" + System.currentTimeMillis() + "]");
        return object;
    }
    
}

方法调用前后输出一下开始时间与结束时间。为了实现我们的需求,实现一下CallbackFilter:

public class DaoFilter implements CallbackFilter {

    @Override
    public int accept(Method method) {
        if ("select".equals(method.getName())) {
            return 0;
        }
        return 1;
    }
    
}

返回的数值表示顺序,结合下面的代码解释,测试代码要修改一下:

public class CglibTest {

    @Test
    public void testCglib() {
        DaoProxy daoProxy = new DaoProxy();
        DaoAnotherProxy daoAnotherProxy = new DaoAnotherProxy();
        
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);
        enhancer.setCallbacks(new Callback[]{daoProxy, daoAnotherProxy, NoOp.INSTANCE});
        enhancer.setCallbackFilter(new DaoFilter());
        
        Dao dao = (Dao)enhancer.create();
        dao.update();
        dao.select();
    }
    
}

意思是CallbackFilter的accept方法返回的数值表示的是顺序,顺序和setCallbacks里面Proxy的顺序是一致的。再解释清楚一点,Callback数组中有三个callback,那么:

  • 方法名为”select”的方法返回的顺序为0,即使用Callback数组中的0位callback,即DaoProxy
  • 方法名不为”select”的方法返回的顺序为1,即使用Callback数组中的1位callback,即DaoAnotherProxy

因此,方法的执行结果为:

StartTime=[1491198489261]
PeopleDao.update()
EndTime=[1491198489275]
Before Method Invoke
PeopleDao.select()
After Method Invoke

因为update()方法不是方法名为”select”的方法,因此返回1,返回1使用DaoAnotherProxy,即打印时间;select()方法是方法名为”select”的方法,因此返回0,返回0使用DaoProxy,即方法调用前后输出两句话。

这里要额外提一下,Callback数组中我特意定义了一个NoOp.INSTANCE,这表示一个空Callback,即如果不想对某个方法进行拦截,可以在DaoFilter中返回2


构造函数不拦截方法

如果Update()方法与select()方法在构造函数中被调用,那么也是会对这两个方法进行相应的拦截的,现在我想要的是构造函数中调用的方法不会被拦截,那么应该如何做?先改一下Dao代码,加一个构造方法Dao(),调用一下update()方法:

public class Dao {
    
    public Dao() {
        update();
    }
    
    public void update() {
        System.out.println("PeopleDao.update()");
    }
    
    public void select() {
        System.out.println("PeopleDao.select()");
    }
}

如果想要在构造函数中调用update()方法时,不拦截的话,Enhancer中有一个setInterceptDuringConstruction(boolean interceptDuringConstruction)方法设置为false即可,默认为true,即构造函数中调用方法也是会拦截的。那么测试方法这么写:

public class CglibTest {

    @Test
    public void testCglib() {
        DaoProxy daoProxy = new DaoProxy();
        DaoAnotherProxy daoAnotherProxy = new DaoAnotherProxy();
        
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);
        enhancer.setCallbacks(new Callback[]{daoProxy, daoAnotherProxy, NoOp.INSTANCE});
        enhancer.setCallbackFilter(new DaoFilter());
        enhancer.setInterceptDuringConstruction(false);
        
        Dao dao = (Dao)enhancer.create();
        dao.update();
        dao.select();
    }
    
}

测试结果:

PeopleDao.update()
StartTime=[1491202022297]
PeopleDao.update()
EndTime=[1491202022311]
Before Method Invoke
PeopleDao.select()
After Method Invoke

看到第一次update()方法的调用,即Dao类构造方法中的调用没有拦截


为什么 Kotlin 使用 cglib 的 Enhancer 动态代理不生效

因为Kotlin的class 默认是final的, fun 默认也是final的,cglib对final的方法,是不走Proxy callback的。

把需要代理的 fun 设置为 open。

open class Dao {
    open fun update() {
        println("PeopleDao.update()")
    }

    open fun select() {
        println("PeopleDao.select()")
    }
}
open class DaoProxy : MethodInterceptor {
    override fun intercept(any: Any, method: Method?, objects: Array<Any?>?, proxy: MethodProxy): Any {
        println("Before Method Invoke")
        proxy.invokeSuper(any, objects)
        println("After Method Invoke")
        return any
    }
}

class DaoAnotherProxy(var target:Any) : MethodInterceptor {
    override fun intercept(any: Any, method: Method, objects: Array<Any>, proxy: MethodProxy): Any {
        println("StartTime=[" + System.currentTimeMillis() + "]")
        proxy.invoke(target, objects)
        println("EndTime=[" + System.currentTimeMillis() + "]")
        return any
    }
}
class DaoFilter : CallbackFilter {
    override fun accept(method: Method): Int {
        return if ("select" == method.name) {
            0
        } else 1
    }
}
fun main() {
    val daoProxy = DaoProxy()
    val daoAnotherProxy = DaoAnotherProxy(Dao())
    val enhancer = Enhancer()
    enhancer.setSuperclass(Dao::class.java)
    enhancer.setCallbacks(arrayOf(daoProxy, daoAnotherProxy, NoOp.INSTANCE))
    enhancer.setCallbackFilter(DaoFilter())
    val dao = enhancer.create() as Dao
    dao.update()
    dao.select()
}

结果:

StartTime=[1625475160864]
PeopleDao.update()
EndTime=[1625475160876]
Before Method Invoke
PeopleDao.select()
After Method Invoke

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 593906浏览

随机文章
SpringSecurity—OAuth 2-Spring Cloud 安全管理
5年前
Axios的简单使用
5年前
Kotlin-类型进阶—单例(二十二)
4年前
Java—并发编程—AQS
4年前
Spring—WebClient使用
2年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 593906 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付