記一次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開發規則
- 在mapper.xml中將namespace設定為mapper.java的全限定名
- 將mapper.java介面的方法名和mapper.xml中statement的id保持一致。
- 將mapper.java介面的方法輸入引數型別和mapper.xml中statement的parameterType保持一致
- 將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,問題就解決了。
總結
動態代理解決問題的檢查點:
- 需要AOP攔截的類是否是final的,final類不可使用CGLIB來代理。
- 是否在給BEAN配AOP的時候強制使用CGLIB,如果是則可指定proxyTargetClass屬性以讓spring強制代理目標類。
- 類是否被多次代理了,如果類被多次代理過,則第二次進行代理的時候拿到的是第一次代理後的物件,這個物件是個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)三點多鐘,接到了一個杭州的電話,是阿裏的。問我是否方便聊聊。我說我在上課,四點下課。然後他就四點多鐘的時候又打了一次過來。項目經歷上來就問我有無大型項目的經歷。不好意思,我說無。。。又