cglib动态代理 | 拦截器

简介: cglib动态代理 | 拦截器

一、代理方法长什么样

代码示例:

//被代理类
class UserService {
  //方法必须能被重写,不能被final修饰
    public void test() {
        System.out.println("调用test方法了...");
    }
}
//拦截器(方法增强)
class MyApiInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("被代理方法执行前"); // 此处可以做一些操作
        Object result = proxy.invokeSuper(obj, args);//执行被代理方法
        System.out.println("被代理方法执行后" );  // 方法调用之后也可以进行一些操作
        return result;
    }
}
//测试
public class TestCglib {
    public static void main(String[] args) {
    //生成一个增强器
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class); //设置要代理的类
        enhancer.setCallback(new MyApiInterceptor()); //设置回调的拦截器
        // 创建代理对象
        Person person=enhancer.create();
        person.test();
    }
}

  刚开始接触动态代理,设想的代理方法是如下的样子,是不是很简单粗暴,这么想是不是也挺合理。

  其实代理方法长这样:

  下面的test方法就是生成的代理方法,其中没有我们写的增强代码,也没有用super.test()去调用被代理方法,而是先获得一个拦截器,然后执行intercept()方法。

既然代理类是运行时自动生成的,为什么不直接把增强代码粘到代理方法中?

  行,但是不优雅。

  拦截器提供了一种可插拔的方式,使增强逻辑与目标方法解耦,能够灵活地对代理逻辑进行配置和管理。

  看看拦截器是如何起作用的。

二、代理方法逻辑

  从上面的例子看,我们调用代理对象的test方法,是如何去执行拦截器【MyApiInterceptor类】中的逻辑呢?

  这要看代理类重写的test方法做了写什么:

 从程序执行的结果上看,图上MethodInterceptor的实现类就是我们自定义的MyApiInterceptor,这里需要说明一下intercept方法的四个参数都是什么:


参数1:代理类对象

参数2:被代理类的方法对象(Method类型对象)

参数3:方法入参(Object[])

参数4:代理类的方法对象(MethodProxy类型对象)

  代理方法如何得到我们自定义的MyApiInterceptor对象?

MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;

 上面是test()方法的第一句代码,其中的(CGLIB$CALLBACK_0)属性是MethodInterceptor类型的,自定义的MyApiInterceptor正好实现了MethodInterceptor接口,这里明显是使用了里式替换。


 看哪里给(CGLIBC A L L B A C K 0 )属性赋值了,可以找到构造函数中挑中了【 C G L I B CALLBACK_0)属性赋值了,可以找到构造函数中挑中了【CGLIBCALLBACK0)属性赋值了,可以找到构造函数中挑中了【CGLIBBIND_CALLBACKS方法】,内容如下:

  //代理类构造函数
    public UserService$$EnhancerByCGLIB$$9391ced0() {
        CGLIB$BIND_CALLBACKS(this);
    }

 现在我们需要知道什么时候在缓存(CGLIB$THREAD_CALLBACKS属性)中存了自定义的MyApiInterceptor对象。


  在代理类中只找到一个静态方法【CGLIB$SET_THREAD_CALLBACKS】给上述属性赋值了,没有在本类中看到谁调用了这个方法。

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

  所以我们要知道,是谁在什么地方调用了静态方法【CGLIB$SET_THREAD_CALLBACKS】

三、代理对象初始化过程

  从demo中看出,创建代理对象是通过enhancer.create();创建的,需要从【Enhancer 类】的【create方法】开始抽丝剥茧找到真相。看个源码和破案一样。

    //生成一个增强器
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class); //设置要代理的类
        enhancer.setCallback(new MyApiInterceptor()); //设置回调的拦截器
        // 创建代理对象
        Person person=enhancer.create();

  方法【createUsingReflection】的第一句代码调用了方法【setThreadCallbacks】,并把代理类的Class对象传给了它。这个方法名的意思就是给ThreadCallbacks赋值,下图看该方法逻辑

 这里确定方法名SET_THREAD_CALLBACKS_NAME就是代理类中的静态方法【CGLIB$SET_THREAD_CALLBACKS】。另外因为是静态方法,反射调用时不用传递代理类对象,传个null就可以。



 标题2中我们的问题我们在这里就可以揭晓。

 是谁在什么时候调用了静态方法【CGLIBS E T T H R E A D C A L L B A C K S 】,给( C G L I B SET_THREAD_CALLBACKS】,给(CGLIBSETTHREADCALLBACKS】,给(CGLIBTHREAD_CALLBACKS属性)中存了自定义的MyApiInterceptor对象?


 who:Enhancer对象

 when:在创建了代理类之后,马上要生成代理类对象之前

四、总结

 CGLIB拦截器的作用主要有以下几个方面:


增加额外的逻辑:

 拦截器可以在目标方法执行前后,或者在方法执行过程中插入额外的逻辑。例如,可以在方法调用前进行权限检查、日志记录等操作,或者在方法调用后进行结果处理、异常处理等操作。通过拦截器,可以在不修改目标类的情况下,增加额外的功能。


实现横切关注点的分离:

 拦截器可以将与核心业务逻辑无关的横切关注点(cross-cuttingconcern)与核心业务逻辑分离。横切关注点包括日志记录、性能统计、事务管理等,这些功能可以通过拦截器统一管理,而不需要在每个目标方法中重复编写代码。


实现代理的灵活配置和管理:

 拦截器提供了一种可插拔的方式,使得增强逻辑可以与目标方法解耦,方便对代理逻辑进行配置和管理。通过拦截器,可以动态地添加、删除或修改代理逻辑,而无需修改目标类的源代码。


 CGLIB拦截器的作用是在代理类中插入增强逻辑,实现与目标方法的拦截、预处理和后处理操作,同时实现了横切关注点的分离和代理的灵活配置。它是CGLIB实现动态代理的重要组成部分,提供了一种强大的方式来扩展和定制代理类的行为。


相关文章
|
存储 芯片
ESP-IDF Modbus从站例子
ESP-IDF Modbus从站例子
397 1
|
Web App开发 iOS开发 Windows
ios获取原生系统应用的包名
ios获取原生系统应用的包名
3068 0
|
4月前
|
SQL 监控 关系型数据库
一键开启百倍加速!RDS DuckDB 黑科技让SQL查询速度最高提升200倍
RDS MySQL DuckDB分析实例结合事务处理与实时分析能力,显著提升SQL查询性能,最高可达200倍,兼容MySQL语法,无需额外学习成本。
|
6月前
|
人工智能 JSON 前端开发
如何解决后端Agent和前端UI之间的交互问题?——解析AG-UI协议的神奇作用
三桥君指出AG-UI协议通过SSE技术实现智能体与前端UI的标准化交互,解决流式传输、实时进度显示、数据同步等开发痛点。其核心功能包括结构化事件流、多Agent任务交接和用户中断处理,具有"一次开发到处兼容"、"UI灵活可扩展"等优势。智能体专家三桥君认为协议将AI应用从聊天工具升级为实用软件,适用于代码生成、多步骤工作流等场景,显著提升开发效率和用户体验。
1455 0
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
800 3
基本的Dos命令 在控制台如何进入某一个文件或者进入不同的盘符
这篇文章介绍了如何在Windows DOS命令行环境中进入不同盘符和文件夹,包括使用Win+R快捷键打开运行窗口、通过输入盘符加冒号(如"D:")切换到指定盘符、使用"cd"命令进入文件夹,以及一些基本的文件操作命令如"dir"查看文件和"del"删除文件等。
基本的Dos命令 在控制台如何进入某一个文件或者进入不同的盘符
|
存储 SQL 关系型数据库
MySQL意向锁是什么?
意向锁用于协调InnoDB存储引擎中的行锁与表锁,避免全表扫描判断行锁的存在,提升性能。主要包括意向共享锁(IS)与意向排他锁(IX),分别在请求行级共享(S)锁与排他(X)锁前加于表级。意向锁自动管理,无需用户干预。例如,事务A锁定一行时先加IS锁,B事务可加IX锁但不能直接加表级X锁。意向锁与行级S/X锁兼容,仅与表级S/X锁冲突。这确保了锁机制高效且减少冲突。
513 0
|
Prometheus 监控 Cloud Native
微服务的监控与可观测性
【8月更文第29天】在微服务架构中,确保每个服务的健康状态和性能表现是非常重要的。为了达到这一目标,我们需要实施一套完整的监控和可观测性方案。本篇文章将介绍如何通过日志、指标和追踪来监测微服务的状态和性能,并提供相应的代码示例。
1040 1
|
SQL Java 数据库连接
SPL介绍
【10月更文挑战第5天】

热门文章

最新文章