最近在做個(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)
異常信息大概意思就是,對(duì)controller使用了jdk代理,要求你使用基于類實(shí)現(xiàn)的代理,其實(shí)就是讓你用CGLIB代理
我們?cè)賮砜聪鲁霈F(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下面配置做對(duì)比,我們這里稱為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)異常肯定是配置文件出現(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ì)注冊(cè)一個(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代理不能對(duì)類進(jìn)行代理所以才報(bào)了llegalStateException異常。
為了驗(yàn)證這個(gè)說法,我們可以看下日志信息。
從日志我們可以看到AspectJAwareAdvisorAutoProxyCreator創(chuàng)建一個(gè)對(duì)UserController的CGBLIB代理和我們定義的DefaultAdvisorAutoProxyCreator創(chuàng)建一個(gè)對(duì)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)在我們?cè)賮砜纯慈罩居惺裁醋兓?/p>
把shiro的配置換成配置2后,我們調(diào)用controller層時(shí)候就沒有報(bào)出異常了。其實(shí)原因也很簡單因?yàn)槲覀儗?duì)controller的代理換成CGLIB代理,那肯定不會(huì)報(bào)錯(cuò)了。
但是這種配置2修改方式,雖然是解決了controller的代理問題,但是其實(shí)還是不好。因?yàn)槲覀兪峭ㄟ^把兩個(gè)AutoProxyCreator對(duì)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)異常啦。再看看日志文件,
現(xiàn)在就只有AspectJAwareAdvisorAutoProxyCreator創(chuàng)建一個(gè)對(duì)UserController的CGLIB代理了,就不存在二次代理的問題了,
即便,在不同spring-dao.xml和spring-shiro.xml里面同時(shí)使用了<aop:config proxy-target-class="true"></aop:config> 也對(duì)一個(gè)bean只有一個(gè)代理。
總結(jié)
配置1是自己創(chuàng)建AutoProxyCreator,shiro官方文檔 和spring集成也是這樣寫。但是這種寫法,就像之前兩篇《spring的bean二次代理問題》和《springAOP應(yīng)盡量避免自己創(chuàng)建AutoProxyCreator》反復(fù)在講,對(duì)于不了解spring源碼來說,是很容易中招,很容易就導(dǎo)致bean的兩次代理問題。所以還是建議采用<aop:config>去代替這種自己創(chuàng)建AutoProxyCreator的方法。shiro的話可以采用配置3就好了,開濤大神的博客的配置也是這樣寫的。
shiro的配置的話也可以參考《spring集成shiro的配置xml》