springboot2.x默認使用的代理是cglib代理操作
背景
因為項目優化,打算寫個日志的切面類,於是起瞭個springboot 工程,在這裡面測試。結果在springboot 裡面測試正常,能正確打印日志,但是把代碼復制到實際項目中,在進入切面打印日志的時候總是報錯,報空指針錯誤。
經調試發現每次都是在獲取註解上的屬性時報錯。當時百思不得解。後來靈光一閃,想到可能是項目中獲取到的是接口方法,而springboot是實現類的method ,所以可以拿到註解的屬性。
但是仔細一想,Springboot裡面也是接口,難道不應該走JDK動態代理嗎?那拿到這個方法的應該也是接口的方法,帶著這個疑問,我開始瞭我的探索之旅。
驗證
springboot 項目
spring 項目
發現springBoot 竟然走的是cglib代理,起代理的是實現類,所以能拿到方法上註解的屬性,而我的項目是個傳統的spring 項目,service是接口,走的是JDK動態代理,通過切點拿到的是接口的方法,而接口上又沒有註解,所以按照springboot的寫法是拿不到註解的,拿不到註解也就拿不到註解屬性,所以報錯。
解決辦法
springboot的寫法
private Method getMethod(ProceedingJoinPoint joinPoint) throws NoSuchMethodException { //獲取方法簽名 Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); return method; } private String getAnnotationDesc(ProceedingJoinPoint joinPoint) throws NoSuchMethodException { Method method = getMethod(joinPoint); String value = method.getAnnotation(MyLog.class).value(); return value; }
spring 的寫法
private Method getMethod(ProceedingJoinPoint joinPoint) throws NoSuchMethodException { //獲取方法簽名 Class<?> targetClass = joinPoint.getTarget().getClass(); String methodName = joinPoint.getSignature().getName(); Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes(); Method method = targetClass.getMethod(methodName, parameterTypes); return method; } private String getAnnotationDesc(ProceedingJoinPoint joinPoint) throws NoSuchMethodException { Method method = getMethod(joinPoint); String value = method.getAnnotation(MyLog.class).value(); return value; }
可以看到spring項目的方法是先獲取目標類,然後再通過目標類獲取目標方法,然後再獲取方法上的註解。
深度追蹤
springboot 為什麼將默認的代理改成瞭cglib,這會導致什麼問題?如果我們想要事務走JDK動態代理,該如何做?
帶著這些疑問,我翻閱瞭springboot的相關issue ,發現很多人提這個問題。
先關issue如下:
issue1
issue2
issue2
springboot團隊之所以默認的代理模式設置成cglib代理,看看spring的官方團隊是怎麼解釋的
This was changed in 1.4 (see 5423). We’ve generally found cglib proxies less likely to cause unexpected cast exceptions.
他們認為使用cglib更不容易出現轉換錯誤。springboot 默認的配置文件的位置在
/org/springframework/boot/spring-boot-autoconfigure/2.1.7.RELEASE/spring-boot-autoconfigure-2.1.7.RELEASE.jar!/META-INF/spring-configuration-metadata.json
{ "name": "spring.aop.proxy-target-class", "type": "java.lang.Boolean", "description": "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).", "defaultValue": true },
如果在事務中強制使用JDK動態代理,以往的知識告訴我們,我們需要將proxyTargetClass 設置成false,於是我們在springboot 中發現註解@EnableTransactionManagement 或者@EnableAspectJAutoProxy默認就為false,說明這裡面的屬性不起作用
@EnableAspectJAutoProxy(proxyTargetClass = false) @EnableTransactionManagement(proxyTargetClass = false)
同理 @EnableCaching 上的proxyTargetClass 屬性也是失效的。如果偏要springboot 走JDK動態代理,那麼需要在application.properties裡面配置
spring.aop.proxy-target-class=false
此時項目中走的就是JDK動態代理。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- springboot通過spel結合aop實現動態傳參的案例
- 基於SpringAop中JoinPoint對象的使用說明
- Java反射及性能詳細
- 淺談基於SpringBoot實現一個簡單的權限控制註解
- 如何使用Spring AOP預處理Controller的參數