一文带你搞懂从动态代理实现到Spring AOP
摘要:本文主要讲了Spring Aop动态代理实现的两种方式。
1. Spring AOP
Spring是一个轻型容器,Spring整个系列的最最核心的概念当属IoC、AOP。可见AOP是Spring框架中的核心之一,在应用中具有非常重要的作用,也是Spring其他组件的基础。AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。
关于AOP的基础知识,并不是本文的重点,我们主要来看下AOP的核心功能的底层实现机制:动态代理的实现原理。AOP的拦截功能是由java中的动态代理来实现的。在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。
那么动态代理是如何实现将切面逻辑(advise)织入到目标类方法中去的呢?下面我们就来详细介绍并实现AOP中用到的两种动态代理。
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。
下面我们分别来示例实现这两种方法。
2. JDK动态代理
2.1 定义接口与实现类
public interface OrderService {
public void save(UUID orderId, String name);
public void update(UUID orderId, String name);
public String getByName(String name);
}
上面代码定义了一个被拦截对象接口,即横切关注点。下面代码实现被拦截对象接口。
public class OrderServiceImpl implements OrderService {
private String user = null;
public OrderServiceImpl() {
}
public OrderServiceImpl(String user) {
this.setUser(user);
}
//...
@Override
public void save(UUID orderId, String name) {
System.out.println("call save()方法,save:" + name);
}
@Override
public void update(UUID orderId, String name) {
System.out.println("call update()方法");
}
@Override
public String getByName(String name) {
System.out.println("call getByName()方法");
return "aoho";
}
}
2.2 JDK动态代理类
public class JDKProxy implements InvocationHandler {
//需要代理的目标对象
private Object targetObject;
public Object createProxyInstance(Object targetObject) {
this.targetObject = targetObject;
return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
this.targetObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被代理对象
OrderServiceImpl bean = (OrderServiceImpl) this.targetObject;
Object result = null;
//切面逻辑(advise),此处是在目标类代码执行之前
System.out.println("---before invoke----");
if (bean.getUser() != null) {
result = method.invoke(targetObject, args);
}
System.out.println("---after invoke----");
return result;
}
//...
}
上述代码实现了动态代理类JDKProxy,实现InvocationHandler接口,并且实现接口中的invoke方法。当客户端调用代理对象的业务方法时,代理对象执行invoke方法,invoke方法把调用委派给targetObject,相当于调用目标对象的方法,在invoke方法委派前判断权限,实现方法的拦截。
2.3 测试
public class AOPTest {
public static void main(String[] args) {
JDKProxy factory = new JDKProxy();
//Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例
OrderService orderService = (OrderService) factory.createProxyInstance(new OrderServiceImpl("aoho"));
//由动态生成的代理对象来orderService 代理执行程序
orderService.save(UUID.randomUUID(), "aoho");
}
}
结果如下:
---before invoke----
call save()方法,save:aoho
---after invoke----
3. CGLIB字节码生成
3.1 要代理的类
CGLIB既可以对接口的类生成代理,也可以针对类生成代理。示例中,实现对类的代理。
public class OrderManager {
private String user = null;
public OrderManager() {
}
public OrderManager(String user) {
this.setUser(user);
}
//...
public void save(UUID orderId, String name) {
System.out.println("call save()方法,save:" + name);
}
public void update(UUID orderId, String name) {
System.out.println("call update()方法");
}
public String getByName(String name) {
System.out.println("call getByName()方法");
return "aoho";
}
}
该类的实现和上面的接口实现一样,为了保持统一。
3.2 CGLIB动态代理类
public class CGLibProxy implements MethodInterceptor {
// CGLib需要代理的目标对象
private Object targetObject;
public Object createProxyObject(Object obj) {
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
//回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法
enhancer.setCallback(this);
//增强后的目标类
Object proxyObj = enhancer.create();
// 返回代理对象
return proxyObj;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Object obj = null;
//切面逻辑(advise),此处是在目标类代码执行之前
System.out.println("---before intercept----");
obj = method.invoke(targetObject, args);
System.out.println("---after intercept----");
return obj;
}
}
上述实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,methodProxy为代理类实例。method.invoke(targetObject, args)通过代理类调用父类中的方法。
3.3 测试
public class AOPTest {
public static void main(String[] args) {
OrderManager order = (OrderManager) new CGLibProxy().createProxyObject(new OrderManager("aoho"));
order.save(UUID.randomUUID(), "aoho");
}
结果如下:
---before intercept----
call save()方法,save:aoho
---after intercept----
4. 总结
本文主要讲了Spring Aop动态代理实现的两种方式,并分别介绍了其优缺点。jdk动态代理的应用前提是目标类基于统一的接口。如果没有该前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
JDK动态代理机制是委托机制,不需要以来第三方的库,只要要JDK环境就可以进行代理,动态实现接口类,在动态生成的实现类里面委托为hanlder去调用原始实现类方法;CGLib 必须依赖于CGLib的类库,使用的是继承机制,是被代理类和代理类继承的关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
参考
本文分享自华为云社区《还不懂Spring AOP?一文带你搞懂动态代理》,原文作者:aoho 。
一文带你搞懂从动态代理实现到Spring AOP的更多相关文章
- CgLib动态代理学习【Spring AOP基础之一】
如果不了解JDK中proxy动态代理机制的可以先查看上篇文章的内容:Java动态代理学习[Spring AOP基础之一] 由于Java动态代理Proxy.newProxyInstance()的时候会发 ...
- 实战 | 一文带你读懂Nginx反向代理
一个执着于技术的公众号 前言 在前面的章节中,我们已经学习了nginx基础知识: 给小白的 Nginx 10分钟入门指南 Nginx编译安装及常用命令 完全卸载nginx的详细步骤 Nginx 配置文 ...
- 一文带你搞懂java中的变量的定义是什么意思
前言 在之前的文章中,壹哥给大家讲解了Java的第一个案例HelloWorld,并详细给大家介绍了Java的标识符,而且现在我们也已经知道该使用什么样的工具进行Java开发.那么接下来,壹哥会集中精力 ...
- Java动态代理学习【Spring AOP基础之一】
Spring AOP使用的其中一个底层技术就是Java的动态代理技术.Java的动态代理技术主要围绕两个类进行的 java.lang.reflect.InvocationHandler java.la ...
- 【springcloud】一文带你搞懂API网关
作者:aCoder2013 https://github.com/aCoder2013/blog/issues/35 前言 假设你正在开发一个电商网站,那么这里会涉及到很多后端的微服务,比如会员.商品 ...
- 一文带你搞懂 Kafka 的系统架构(深度好文,值得收藏)
Kafka 简介 Kafka 是一种高吞吐.分布式.基于发布和订阅模型的消息系统,最初是由 LinkedIn 公司采用 Scala 和 java 开发的开源流处理软件平台,目前是 Apache 的开源 ...
- 一文带你搞懂 RPC 到底是个啥
RPC(Remote Procedure Call),是一个大家既熟悉又陌生的词,只要涉及到通信,必然需要某种网络协议.我们很可能用过HTTP,那么RPC又和HTTP有什么区别呢?RPC还有什么特点, ...
- 一文带你搞懂 SSR
欲语还休,欲语还休,却道天凉好个秋 ---- <丑奴儿·书博山道中壁>辛弃疾 什么是 SSR ShadowsocksR?阴阳师?FGO? Server-side rendering (SS ...
- 一文带你搞懂 Google 发布的新开源项目 GUAC
随着软件供应链攻击的显著增加,以及 Log4j 漏洞带来的灾难性后果和影响,软件供应链面临的风险已经成为网络安全生态系统共同关注的最重要话题之一.根据业内权威机构 Sonatype 发布的2022软件 ...
- 从定义到AST及其遍历方式,一文带你搞懂Antlr4
摘要:本文将首先介绍Antlr4 grammer的定义方式,如何通过Antlr4 grammer生成对应的AST,以及Antlr4 的两种AST遍历方式:Visitor方式和Listener方式. 1 ...
随机推荐
- 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(9) -- 实现系统动态菜单的配置和权限分配
在WPF应用端开发,它的界面类似于Winform端,因此我们也需要对系统的菜单进行动态配置,这样才能把系统的功能弹性发挥到极致,通过动态菜单的配置方式,我们可以很容易的为系统新增所需的功能,通过权限分 ...
- WPF性能优化:Freezable 对象
Freezable是WPF中一个特殊的基类,用于创建可以冻结(Freeze)的可变对象.冻结一个对象意味着将其状态设置为只读,从而提高性能并允许在多线程环境中共享对象. Freezable的应用 我们 ...
- 阿里发布AI编码助手:通义灵码,兼容 VS Code、IDEA等主流编程工具
今天是阿里云栖大会的第一天,相信场外的瓜,大家都吃过了.这里就不说了,有兴趣可以看看这里:云栖大会变成相亲现场,最新招婿鄙视链来了... . 这里主要说说阿里还发布了一款AI编码助手,对于我们开发者来 ...
- PC电脑端如何多开Skype,一步搞定!
由于工作原因,本人经常会用到Skype来联系客户,目前有两个账号需要同时登录. 但是,Skype默认只能登录一个账号,而且安装的时候也不能自定义安装地址,所以没办法同时登录两个. 有的朋友可能会想到直 ...
- elrond32
前置知识 int __cdecl main(int argc, char **argv) * argc: 整数, 为传给main()的命令行参数个数.* argv: 字符串数组.argv[0] 为程序 ...
- Java基础知识(纯干货)
基础篇 IDEA 开发 Java项目 卸载JDK 删除Java的安装目录 删除JAVA_HOME 删除path下关于java的目录 java -version 安装JDK17 下载链接:https:/ ...
- 写代码不用"if"行不行,曾经的反 if 运动
如果在IT行业的时间够长的话,可能还记得大约10几年前,设计模式风靡一时的时候,有过一段反 "if" 的运动. 所谓的反"if"运动,其实是夸大了"i ...
- 【外包杯】【无语的报错】意想不到的逗号Unexpected comma.(已解决)
解决了,答案是没保存,看见那些文件是型号了吗,就是这个原因.
- list.add()语句作用
----该方法用于向集合列表中添加对象 示例 本示例使用List接口的实现类ArrayList初始化一个列表对象,然后调用add方法向该列表中添加数据. public static void mai ...
- 容器网络Cilium:DualStack双栈特性分析
本文分享自华为云社区<容器网络Cilium入门系列之DualStack双栈特性分析>,作者: 可以交个朋友. 一 . 关于IPV6/IPV4 双栈 目前很多公司开始将自己的业务由ipv4切 ...