1. 程式人生 > >記一次Spring aop的所遇到的問題

記一次Spring aop的所遇到的問題

由來

專案中需要實現某個訂單的狀態改變後然後推送給第三方的功能,由於更改狀態的專案和推送的專案不是同一個專案,所以為了不改變原專案的程式碼,我們考慮用spring的aop來實現。

專案用的是springmvc + spring + mybatis 的架構,我們知道spring實現了兩種代理方式:JDK動態代理和CGLB動態代理。所以spring對介面和類都可以實現代理。所以只需要考慮在DAO介面的相關update狀態的方法上加aop就可以了。整理了下共有六個地方對訂單的status做了update。所以配置如下:

    <!-- 宣告通知類 -->
    <bean  class
="com.info.web.service.BorrowOrderStatusAspect"></bean> <aop:config> <aop:aspect ref="aspectBean"> <aop:pointcut expression="execution(public * com.info.risk.dao.IRiskCreditUserDao.updateAssetsSuc(..))
|| execution(public * com.info.risk.dao.IRiskCreditUserDao.updateAssetsFail(..))
|| execution(public * com.info.web.dao.IBorrowOrderDao.updateByPrimaryKeySelective(..))
|| execution(public * com.info.web.dao.IBorrowOrderDao.updateByPrimaryKeyWithBLOBs(..))
|| execution(public * com.info.web.dao.IBorrowOrderDao.updateByPrimaryKey(..))" id
="servicePointcut" /> <aop:after-returning method="doAfter" pointcut-ref="servicePointcut" /> </aop:aspect> </aop:config>

 可是啟動專案的時候發現,啟動失敗,報錯資訊如下:

………………………………

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 
'repaymentService': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.info.web.dao.IBorrowOrderDao com.info.web.service.RepaymentService.borrowOrderDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'IBorrowOrderDao': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy39]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1210) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194) at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533) ... 37 more Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.info.web.dao.IBorrowOrderDao com.info.web.service.RepaymentService.borrowOrderDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'IBorrowOrderDao': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy39]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331) ... 48 more Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'IBorrowOrderDao': Post-processing of FactoryBean's singleton object failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy39]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39 at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:116) at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1517) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:251) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194) at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1120) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1044) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533) ... 50 more Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy39]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39 at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:212) at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:109) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:447) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:333) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:293) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1719) at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:113) ... 57 more Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy39 at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:446) at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33) at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216) at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377) at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:317) at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:202) ... 64 more

分析原因

從報錯資訊可以瞭解說是代理了final修飾的類。可是哪裡來的final類? 原來,DAO層使用的是mybatis,可以只寫介面不用寫實現類。而我們專案中就是沒有寫實現類。但是spring也可以對介面進行代理,繼續分析。

Mapper開發規則

  1. 在mapper.xml中將namespace設定為mapper.java的全限定名 
  2. 將mapper.java介面的方法名和mapper.xml中statement的id保持一致。 
  3. 將mapper.java介面的方法輸入引數型別和mapper.xml中statement的parameterType保持一致 
  4. 將mapper.java介面的方法輸出 結果型別和mapper.xml中statement的resultType保持一致。

注意遵循上邊四點規範!這樣拋棄Dao實現類的寫法: 具有更好的可擴充套件性,提高了靈活度。

先來說明下mybatis為何可以只寫介面而不寫實現類,通過mybatis原始碼分析可知:

mybatis通過JDK的動態代理方式,在啟動載入配置檔案時,根據配置mapper的xml去生成Dao的實現。session.getMapper()使用了代理,當呼叫一次此方法,都會產生一個代理class的instance,看看這個代理class的實現.

 1 public class MapperProxy implements InvocationHandler { 
 2 ... 
 3 public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) { 
 4     ClassLoader classLoader = mapperInterface.getClassLoader(); 
 5     Class<?>[] interfaces = new Class[]{mapperInterface}; 
 6     MapperProxy proxy = new MapperProxy(sqlSession); 
 7     return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy); 
 8   } 
 9 
10 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
11     if (!OBJECT_METHODS.contains(method.getName())) { 
12       final Class<?> declaringInterface = findDeclaringInterface(proxy, method); 
13       final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession); 
14       final Object result = mapperMethod.execute(args); 
15       if (result == null && method.getReturnType().isPrimitive()) { 
16         throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" +    method.getReturnType() + ")."); 
17       } 
18       return result; 
19     } 
20     return null; 
21   } 

這裡是用到了JDK的代理Proxy。 newMapperProxy()可以取得實現interfaces 的class的代理類的例項。

當執行interfaces中的方法的時候,會自動執行invoke()方法,其中public Object invoke(Object proxy, Method method, Object[] args)中 method引數就代表你要執行的方法.

MapperMethod類會使用method方法的methodName 和declaringInterface去取 sqlMapxml 取得對應的sql,也就是拿declaringInterface的類全名加上 sql-id..

因此,dao類被多次代理,第二次aop進行代理的時候拿到的是第一次代理後的物件,這個物件是個final形式的,因此報錯。

解決方法:最後我在外層封裝了一個service介面和介面的實現類,將dao注入到該service中,最後對該service實現aop,問題就解決了。

總結

動態代理解決問題的檢查點: 

  1. 需要AOP攔截的類是否是final的,final類不可使用CGLIB來代理。
  2. 是否在給BEAN配AOP的時候強制使用CGLIB,如果是則可指定proxyTargetClass屬性以讓spring強制代理目標類。
  3. 類是否被多次代理了,如果類被多次代理過,則第二次進行代理的時候拿到的是第一次代理後的物件,這個物件是個final形式的,所以會出現這個錯誤。

基於第三點要注意,類是否被多次代理不緊緊取決於類是否被配置了多次AOP,如果類實現了某個介面,則還要看類實現的介面是否被aop攔截過。如果類實現了介面且介面也被AOP攔截了,則很可能出現上面的錯誤(是否出錯取決於AOP代理執行的順序)。

spring配置aop需要注意:

1、proxy-target-class屬性值決定是基於介面的還是基於類的代理被建立,啟動對@Aspectj的支援 true為cglib(基於類),false為jdk代理(基於介面),不寫的話預設為false。為true的話,會導致攔截不了mybatis的mapper

<aop:aspectj-autoproxy proxy-target-class="false" />

2、在類沒有實現任何介面,並且沒有預設建構函式的情況下,通過建構函式注入時,目前的Spring是無法實現AOP切面攔截的。 參考通過CGLIB實現AOP的淺析(順便簡單對比了一下JDK的動態代理)

相關推薦

Spring aop遇到的問題

由來 專案中需要實現某個訂單的狀態改變後然後推送給第三方的功能,由於更改狀態的專案和推送的專案不是同一個專案,所以為了不改變原專案的程式碼,我們考慮用spring的aop來實現。 專案用的是springmvc + spring + mybatis 的架構,我們知道sprin

Spring Cloud壓力測試

prope erro 整體 not 能夠 nbsp 隔離 緩存 大量 前言 公司打算舉辦一場活動,現場參與活動人數比較多。針對於可能訪問比較密集的接口進行壓力測試。使用jmeter進行測試,請求並發稍微多些,系統就會掛起。   針對壓力測試出現的問題,因為並發

Spring配置事故

vcc @override iba initial web color 差異 info ida 在引入Spring的Validated時,需要聲明如下bean: @Beanpublic MethodValidationPostProcessor methodVa

Spring 事務配置踩坑

pub lock loaded 查詢條件 setprop ins 能夠 get 數據 記一次 Spring 事務配置踩坑記 問題描述:(SpringBoot + MyBatisPlus) 業務邏輯偽代碼如下。理論上,插入數據 t1 後,xxService.getXxx(

spring註解開啟失敗的經歷

1.專案中註解開啟事務配置   如有多個org.springframework.jdbc.datasource.DataSourceTransactionManager時候,要制定bean名稱,例: @Transaction(“transactionManager”) 2.spring中註

Spring refresh context引發Data source is closed異常的坑

背景:Spring的profile寫在了自定義配置檔案中,需要手動讀取配置檔案,手動啟用profile,關於啟用profile的方法參考Spring啟用profile的幾種方式,我們採用了方案2。程式是首先初始化Spring上下文,建立連線池和事務管理器等等,然後讀取配置檔案,獲

spring boot中MongoDB Prematurely reached end of stream的異常解決

  在spring boot專案中使用了mongodb,當一段時間沒有操作mongodb,下次操作mongodb時就會出現異常。異常如下: org.springframework.data.mongodb.UncategorizedMongoDbException: Prematurely reached

Spring Cloud負載均衡時服務提供者使用fastjson,服務消費者請求時出現的異常

服務消費者出現的異常:2018-05-22 17:33:16.605  INFO 6160 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints

Spring Websocket後臺伺服器CPU佔用率過高的問題排查過程

背景 最近在做Spring Websocket後臺程式的壓力測試,但是當併發數目在10個左右時,伺服器的CPU使用率一直在160%+,出現這個問題後,一開始很納悶,雖然伺服器配置很低,但也不至於只有10個併發吧。。伺服器的主要配置如下: CPU:2核 In

篇文章學會Spring+SpringMVC+Mybatis+Maven搭建和部署,Spring+SpringMVC+Mybatis+Maven的整合

之前一直沒有用過maven和Mybatis最近自己搭建一個基於Maven的Spring+SpringMVC+Mybatis開發環境。注:有的時候沒有自己動手真正搭過環境(脫離教學的方式),可能有些地方的問題注意不到的。會在介紹搭建的同時記錄一些遇見的坑和一些知識點。首先放上M

spring boot 配置失誤導致的異常org.apache.ibatis.binding.BindingException: Invalid bound statement

在整合spring boot 和mybatis 時 ,進行單元測試時,報錯: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.yunda.study.

Spring Cloud Session微服務間傳遞丟失問題定位

       在構建基於Spring Cloud微服務框架時,使用了常用的框架NGINX+ZUUL+Eureka+業務服務,Session使用Spring boot的Redis整合,所有微服務間共享Session。    所有業務的微服務Rest介面前臺呼叫介面通過ZUUL進

使用crontab計劃任務執行python指令碼問題及處理的過程

今天把一個python指令碼遷移到Centos7,用crontab執行,期間遇到很多錯誤,最終把所遇問題一一處理,感覺有必要把處理過程記錄下來 1、問題環境 Centos7 x64 python2.7 和python 3.5 有安裝virtualenvwrappe

IDEA Spring 專案從建立到打包為 war

首先是建立 Maven Java Web 專案 首先是建立新專案,點選”create new project”,左側選擇型別為”Maven”,右側上方選擇自己的”Project SDK”,單擊”next” “GroupId”類似於Java的package,此處填club.seliote,”Arti

spring cloud @PostConstruct 執行兩的分析

1、前言 最近遇到一個情況,@PostConstruct 方法會被執行兩次,感覺有點奇怪,跟蹤程式碼簡單分析了下,場景有點特殊,這裡記錄下。 @PostConstruct屬於JSR250規範,在bean建立完成並且屬性賦值完成之後會執行該初始化方法。 內部通

移動端H5開發遇到的問題與細節,以及ios相容

第一次弄移動端H5專案開發,雖然是簡單的頁面展示,但也遇到了很多細節問題與相容問題,尤其是h5視訊標籤video, 一.移動端禁止縮放 相容 <meta name="viewport" content="width=device-width,initial-scal

整合spring-amqp後出現執行緒池為正常關閉。導致tomcat無法正常關閉顯示記憶體洩露的問題

起因:因為這幾天閒來無事,所以想著改造下舊專案的訂單自動取消功能,原本是通過定時任務輪詢掃描未支付訂單的,及時性不足並且浪費資料庫io的資源,所以就想用rabbitmq的死信佇列來完成延遲自動取消的功能。於是隨手copy了一段spring-amqp的Java Configur

遞迴在我專案中發揮的作用

我的github地址:Github 背景 在最近的專案中,有這麼一個功能點,就是要獲取在WEB IDE裡使用者所寫的註釋中的一段特殊規則,然後解析成一段JS config 物件 例如: //% width="100px" height="200px" //% pos.top="50px"

mapreduce讀取不到輸入文件的問題

mapreduce 過濾器hdfs上輸入文件所在包含兩個目錄,分別是: /20170503/shoplast/ /20170503/shop/但是我想過濾掉shop,只把shoplast作為輸入故我實現了過濾器如下: public static class Fi

阿裏電面經歷

art 存儲空間 自由 結束 eas 大型 特定 easy col 昨天下午(3/19)三點多鐘,接到了一個杭州的電話,是阿裏的。問我是否方便聊聊。我說我在上課,四點下課。然後他就四點多鐘的時候又打了一次過來。項目經歷上來就問我有無大型項目的經歷。不好意思,我說無。。。又