在springmvc+shiro項(xiàng)目中使用druid監(jiān)聽controller層出現(xiàn)IllegalStateException異常

最近在做個(gè)spring+springmvc+shiro的整合,后面想再加個(gè)druid去監(jiān)聽spring,當(dāng)去監(jiān)聽的controller層的時(shí)候就報(bào)出了IllegalStateException異常

異常信息大概如下:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.
lang.IllegalStateException: The mapped handler method class 'com.zjw.shiro.controller.UserController' is 
not an instance of the actual controller bean class 'com.sun.proxy.$Proxy26'. If the controller requires
 proxying (e.g. due to @Transactional), please use class-based proxying.
HandlerMethod details: 
Controller [com.zjw.shiro.controller.UserController]
Method [public java.lang.String com.zjw.shiro.controller.UserController.login(com.zjw.shiro.entity.User,
javax.servlet.http.HttpServletRequest,org.springframework.web.servlet.mvc.support.RedirectAttributes)]
Resolved arguments: 
[0] [type=com.zjw.shiro.entity.User] [value=com.zjw.shiro.entity.User@59943548]
[1] [type=org.apache.shiro.web.servlet.ShiroHttpServletRequest] [value=org.apache.shiro.web.servlet.ShiroHttpServletRequest@2a4b3e88]
[2] [type=org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap] [value={}]

    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)

異常信息大概意思就是,對controller使用了jdk代理,要求你使用基于類實(shí)現(xiàn)的代理,其實(shí)就是讓你用CGLIB代理

我們再來看下出現(xiàn)報(bào)錯(cuò)信息的配置文件

druid相關(guān)配置
<!-- 開啟spring監(jiān)控  -->
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor"></bean>
 <bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype">
  <property name="patterns">
    <list>
      <value>com.zjw.shiro.controller.*</value>
      <value>com.zjw.shiro.service.*</value>
      <value>com.zjw.shiro.mapper.*</value>
    </list>
   </property>
</bean>

<aop:config>
  <aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut"/>
</aop:config>
shiro相關(guān)配置

因?yàn)椋c下面配置做對比,我們這里稱為shiro配置1

<!-- 開啟Shiro注解 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>

 <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
   <property name="securityManager" ref="securityManager"/>
 </bean>

出現(xiàn)異??隙ㄊ桥渲梦募霈F(xiàn)了問題。
從我們上一篇文章《JDK和CGLIB生成動(dòng)態(tài)代理類的區(qū)別以及Spring動(dòng)態(tài)代理機(jī)制》可以知道,
Spirng的AOP的動(dòng)態(tài)代理實(shí)現(xiàn)機(jī)制也是這兩種:JDK動(dòng)態(tài)代理和CGLib動(dòng)態(tài)代理
一般而言Spring默認(rèn)優(yōu)先使用JDK動(dòng)態(tài)代理技術(shù),只有在被代理類沒有實(shí)現(xiàn)接口時(shí),才會(huì)選擇使用CGLIB技術(shù)來實(shí)現(xiàn)AOP。
我們的controller層是沒有實(shí)現(xiàn)接口,應(yīng)該是用CGLIB代理,而spring的AOP會(huì)自動(dòng)根據(jù)運(yùn)行類選擇 JDK 或 CGLIB 代理,那應(yīng)該會(huì)自動(dòng)選擇CGLIB代理啊。
但我們的異常信息卻要求是我們使用基于類實(shí)現(xiàn)的代理——CGLIB代理,所以可以肯定是現(xiàn)在我們使用上面配置文件,因?yàn)槟承┰蚨鴮?dǎo)致我們使用了jdk代理。

《spring的bean二次代理問題》《springAOP應(yīng)盡量避免自己創(chuàng)建AutoProxyCreator》之前這兩篇文章可以知道,spring的配置文件配置不當(dāng),容易導(dǎo)致spring的bean二次代理問題,那么是不是我們配置文件也出現(xiàn)問題,所以導(dǎo)致了出現(xiàn)了多個(gè)ProxyCreator,出現(xiàn)了沖突,讓controller層使用的是jdk代理,所以最后出現(xiàn)上面IllegalStateException異常呢?

從上面druid相關(guān)配置,我們可以看到<aop:config><aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut"/>
</aop:config>這句配置,從之前的《springAOP應(yīng)盡量避免自己創(chuàng)建AutoProxyCreator》就可以知道,這句代碼是會(huì)注冊一個(gè)AutoProxyCreator、
而shiro相關(guān)配置有一句<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>也定義了一個(gè)DefaultAdvisorAutoProxyCreator,而且因?yàn)閜roxy-target-class的缺省,那肯定使用的是jdk代理。
所以有可能是兩個(gè)代理起了沖突,導(dǎo)致當(dāng)我們druid想去監(jiān)控controller的時(shí)候是使用jdk代理,jdk代理不能對類進(jìn)行代理所以才報(bào)了llegalStateException異常。

為了驗(yàn)證這個(gè)說法,我們可以看下日志信息。

AspectJAwareAdvisorAutoProxyCreator創(chuàng)建一個(gè)對UserController的CGLIB代理.png

我們定義的DefaultAdvisorAutoProxyCreator創(chuàng)建一個(gè)對UserController的JDK代理.png

從日志我們可以看到AspectJAwareAdvisorAutoProxyCreator創(chuàng)建一個(gè)對UserController的CGBLIB代理和我們定義的DefaultAdvisorAutoProxyCreator創(chuàng)建一個(gè)對UserController的JDK代理,一個(gè)UserController的bean有兩個(gè)不同的代理,所以起了沖突。
毫無疑問,肯定是配置文件問題導(dǎo)致了bean的兩次代理問題。

如何解決:

我們把配置文件修改一下再看日志會(huì)發(fā)生什么不同

druid配置保存不變,shiro還是自己創(chuàng)建DefaultAdvisorAutoProxyCreator,只是稍微做了修改
我們這里稱為shiro配置2

<!-- 開啟Shiro注解 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
  <!-- 新增了這句代碼,這句代碼意思是 這個(gè)屬性為true時(shí),表示被代理的是目標(biāo)類本身而不是目標(biāo)類的接口,實(shí)際就是強(qiáng)制為CGLIB代理->
  <property name="proxyTargetClass" value="true"/>
</bean>
 <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
   <property name="securityManager" ref="securityManager"/>
 </bean>

可以看到配置2,是多一個(gè)代碼<property name="proxyTargetClass" value="true"/>。這句代碼意思是 這個(gè)屬性為true時(shí),表示被代理的是目標(biāo)類本身而不是目標(biāo)類的接口,實(shí)際就是強(qiáng)制為CGLIB代理。

現(xiàn)在我們再來看看日志有什么變化。

AspectJAwareAdvisorAutoProxyCreator創(chuàng)建一個(gè)對UserController的CGLIB代理.png

我們定義的DefaultAdvisorAutoProxyCreator創(chuàng)建一個(gè)對UserController的CGLIB代理.png

把shiro的配置換成配置2后,我們調(diào)用controller層時(shí)候就沒有報(bào)出異常了。其實(shí)原因也很簡單因?yàn)槲覀儗ontroller的代理換成CGLIB代理,那肯定不會(huì)報(bào)錯(cuò)了。
但是這種配置2修改方式,雖然是解決了controller的代理問題,但是其實(shí)還是不好。因?yàn)槲覀兪峭ㄟ^把兩個(gè)AutoProxyCreator對controller的代理都編程CGLIB,所以才沒有報(bào)錯(cuò)。
但是這是一種治標(biāo)不治本的問題,因?yàn)檫€是存在bean二次代理。其實(shí)問題就在于AutoProxyCreator,我們定義兩個(gè),導(dǎo)致bean二次代理。

真正的解決方法的就是
《springAOP應(yīng)盡量避免自己創(chuàng)建AutoProxyCreator》這一篇文章所說的,避免自己創(chuàng)建AutoProxyCreator,直接采用<aop;config>就好了,這樣一來配置文件寫方便,也避免了bean的二次代理問題。

所以修改后的shiro配置3
<aop:config proxy-target-class="true"></aop:config>  
<bean class="  org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  
    <property name="securityManager" ref="securityManager"/>  
</bean>   

修改成配置3之后當(dāng)然也是沒有報(bào)異常啦。再看看日志文件,


只有AspectJAwareAdvisorAutoProxyCreator創(chuàng)建一個(gè)對UserController的CGLIB代理.png

現(xiàn)在就只有AspectJAwareAdvisorAutoProxyCreator創(chuàng)建一個(gè)對UserController的CGLIB代理了,就不存在二次代理的問題了,
即便,在不同spring-dao.xml和spring-shiro.xml里面同時(shí)使用了<aop:config proxy-target-class="true"></aop:config> 也對一個(gè)bean只有一個(gè)代理。

總結(jié)

配置1是自己創(chuàng)建AutoProxyCreator,shiro官方文檔 和spring集成也是這樣寫。但是這種寫法,就像之前兩篇《spring的bean二次代理問題》《springAOP應(yīng)盡量避免自己創(chuàng)建AutoProxyCreator》反復(fù)在講,對于不了解spring源碼來說,是很容易中招,很容易就導(dǎo)致bean的兩次代理問題。所以還是建議采用<aop:config>去代替這種自己創(chuàng)建AutoProxyCreator的方法。shiro的話可以采用配置3就好了,開濤大神的博客的配置也是這樣寫的。

shiro的配置的話也可以參考《spring集成shiro的配置xml》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,661評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,282評論 6 342
  • 什么是Spring Spring是一個(gè)開源的Java EE開發(fā)框架。Spring框架的核心功能可以應(yīng)用在任何Jav...
    jemmm閱讀 16,779評論 1 133
  • 我喜歡一本正經(jīng)的瞎搞 我更喜歡情不自禁的胡鬧
    愛嗨愛嗨呦閱讀 221評論 0 0
  • #作業(yè)#“每個(gè)人的行為背后都有其隱性動(dòng)機(jī),每一個(gè)行為都是其現(xiàn)有認(rèn)知能力下對自己利益最大化的考量。從隨處可見...
    張寶英閱讀 230評論 1 1

友情鏈接更多精彩內(nèi)容