spring initializr找不到aop_学完 Aop,连动态代理的原理都不懂?

本文是Spring Aop系列的最后一部分,探讨了动态代理原理,包括JDK和CGLIB代理。作者强调Spring Boot 2.0默认使用CGLIB代理,并提供了配置切换为接口代理的方法。此外,文章解释了为什么需要动态代理,静态代理的局限性,以及JDK和CGLIB代理的工作方式。通过对示例代码的分析,帮助读者理解动态代理如何在不修改原始类的情况下增强方法行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(给ImportNew加星标,提高Java技能)

作者:饭谈编程/稀饭下雪(本文系作者投稿)

大前提

该文章是必须要懂的SpringAop系列的最后一篇文章,第一篇文章是你必须要懂的Spring:Aop,第二篇文章是你必须要懂的Spring-Aop之源码跟踪分析Aop,最后一篇文章我们将会揭露Aop的原理,也就是动态代理。希望看完这最后一篇文章,大家对SpringAop都有一个全面的掌握,不管面试官如何问,都可以屌回去 ━━( ̄ー ̄*|||━━

温馨提示:前面没看的最好点击浏览下!!!

说个坑爹的事

大家都知道,我有个习惯,在动手写一篇文章之前会先将该文章相关的资料仔细琢磨一遍,然后再结合源码再调试一遍,结果,说好的

74349ac1878c195c77ede301b5f3fabb.png

看源码也确实是

b0d53c0ac354d5911c37ad6cbfe332b1.png

源码确实有进行了是否是接口的判断,但是问题来了,我调试的时候发现无论代理类是否有接口,最终都会被强制使用CGLIB代理,没办法,只能翻看SpringBoot的相关文档,最终发现原来SpringBoot从2.0开始就默认使用Cglib代理了,好家伙,怪不得我调试半天找不到原因。

那么如何解决呢?肯定是通过配置啦,按照如下配置即可

在application.properties文件中配置 spring.aop.proxy-target-class=false

即可。

【划重点】 曾经遇见过面试官问,SpringBoot默认代理类型是什么?看完该篇文章,我们就可以果断的回答是Cglib代理了。通过调试代码发现的规则,我想我这辈子都不会忘记这个默认规则。

动态代理原理剖析

什么是代理

简单来说,就是在运行的时候为目标类动态生成代理类,而在操作的时候都是操作代理类,代理模式有个显而易见的好处,那便是可以在不改变对象方法的情况下对方法进行增强。试想下,我们在你必须要懂的Spring-Aop有提到使用Aop来做权限认证,如果不用Aop,那么我们就必须要为所有需要权限认证的方法都加上权限认证代码,听起来就觉得蛋疼,你觉得对不对?

为什么不用静态代理

静态代理类不是说不可以用,如果只有一个类需要被代理,那么自然可以用,如这是在你必须要懂的Spring-Aop使用的一个例子类,该类的作用只是打印出我要买东西。

492e858f3010306e35601cfb037090ef.png

代理类如下

8f754fa678e95c7317650236606ded97.png

可以看到这个BuyProxy代理类只是塞了一个IBuyServcie接口进行,而且自身也实现了接口IBuyService,而在buyItem方法被调用的时候会先做自己的操作,再调用塞进去的接口的buyItem方法。

测试类很简单,如下

249a3174fa67c121cf6bcb643a9f950d.png

运行后很自然而然的打印出

581d723e90413315c160d237c4212c62.png

静态代理就是简单,但是弊端也很明显,如果有多个类都需要同样的代理,都实现了同样的接口,那么如果使用静态代理的话,我们就要构造多个Proxy类,就会造成类爆炸。

而使用了Aop后,也就是动态代理后,便可以一次性解决该问题了,具体可以看你必须要懂的Spring-Aop中的操作方法。

JDK动态代理原理

这里给出一个JDK动态代理的demo

首先给出一个简单的业务类,Hello类和接口

ae4ba295da36664748648786724cf488.png

1e06cf514d30f096e460f79053cfe409.png

真正实现了类的代理功能的其实就是这个实现了接口InvocationHandler的JdkProxy类

ecfc0eb30770b1e668d699aa5510f754.png

我们可以看到其中必须实现的方法是invoke,可以看到invoke方法的参数带有Method对象,这个就是我们的目标Method,现在我们的目的就是要在这个Method在被调用前后实现我们的业务,可以看到在method.invoke反调前后实现了before和after业务。

这里再给出一个Main测试类,作用是取得Hello的代理类,然后调用其中的say方法。

c3f26166e79b9d2c8fc5646e9ad05bfa.png

运行结果如下

6c8e7f3d3589cea8215a26d61f1449d0.png

原理很简单 在JdkProxyMain中hello调用say的时候,由于Hello已经被“代理”了,所以在调用say函数的时候其实是调用JdkProxy类中的invoke函数,而在invoke函数中先是实现了before函数才实现Object result = method.invoke(target, args),这一句其实是调用say函数,而后才实现after函数,于是这样就可以不必在改动目标类的前提下实现代理了,并且不会像静态代理那样导致类爆炸。

CGLIB动态代理原理

先给出一个Cglib动态代理的demo

9d45ce6c7bbd1b9fb124242b4df801dc.png

「思考题一」 

为什么CGLIB代理可以直接对类进行代理,而JDK代理却一定要实现接口呢?答案见问末!!!

核心类是实现了MethodInterceptor的CGlibProxy类

bfc2d4195d7e9369c34d66a5ab7e41af.png

可以看到其中实现了方法intercept,先是在目标函数被调用前实现自己的业务,比如before()和after(),之后再通过 proxy.invokeSuper(obj, args) 触发目标函数。

「思考题二」

为什么这里不像JDK代理那样,直接使用反射[method.invoke(target, args)]触发目标函数?答案见问末!!!

最后给出入口类

7d14f44ec609c5c536dfb5dcc2ecd80a.png

最后给出运行类,运行类如下

146a67e036b3e62a4c151bfb3d9ca3b5.png

可以看到运行结果

23426074eef55da85943453fdca74aea.png

原理很简单 在CglibProxyMain中hello调用say的时候,由于Hello已经被“代理”了,所以在调用say函数的时候其实是调用CGlibProxy类中的intercept函数。

总结

动态代理的相关原理已经讲解完毕,相关测试源码用可以点击原文。接下来让我们回答以下几个思考题。

「思考解惑一」为什么CGLIB代理可以直接对类进行代理,而JDK代理却一定要实现接口呢?

可以从我们上面的例子看出,在JdkProxy类中取得代理类的方式是 (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this) 而在CGlibProxy类中取得代理类的方式是 (T) Enhancer.create(cls, this) 两种取得代理类的方式不同,导致了一个需要实现接口,一个不需要。

「思考解惑二」为什么这里不像JDK代理那样,直接使用反射[method.invoke(target, args)]触发目标函数? 

答案较长,看下面分析!

首先要进行反射触发函数,要取得对应的method,以及该method所属对象,也就是target,再次是args方法参数,而我们看下调试界面

bc8bf20c5883321e8ff51b1ee4ba0dd0.png

可以从界面看到,目标对象obj并不是Hello对象,二是被CGLIB代理过的对象,因此无法像JDK代理那样直接通过反射搞定。

推荐阅读

(点击标题可跳转阅读)

Java核心技术点之动态代理

JAVA 动态代理

分析动态代理给 Spring 事务埋下的坑

看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

8ccec3d00e330de5fdef41dac1ce8efb.png

好文章,我在看❤️

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值