最近在做个spring+springmvc+shiro的整合,后面想再加个druid去监听spring,当去监听的controller层的时候就报出了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代理,要求你使用基于类实现的代理,其实就是让你用CGLIB代理
我们再来看下出现报错信息的配置文件
druid相关配置
<!-- 开启spring监控 -->
<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相关配置
因为,与下面配置做对比,我们这里称为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>
出现异常肯定是配置文件出现了问题。
从我们上一篇文章《JDK和CGLIB生成动态代理类的区别以及Spring动态代理机制》可以知道,
Spirng的AOP的动态代理实现机制也是这两种:JDK动态代理和CGLib动态代理
一般而言Spring默认优先使用JDK动态代理技术,只有在被代理类没有实现接口时,才会选择使用CGLIB技术来实现AOP。
我们的controller层是没有实现接口,应该是用CGLIB代理,而spring的AOP会自动根据运行类选择 JDK 或 CGLIB 代理,那应该会自动选择CGLIB代理啊。
但我们的异常信息却要求是我们使用基于类实现的代理——CGLIB代理,所以可以肯定是现在我们使用上面配置文件,因为某些原因而导致我们使用了jdk代理。
从《spring的bean二次代理问题》和《springAOP应尽量避免自己创建AutoProxyCreator》之前这两篇文章可以知道,spring的配置文件配置不当,容易导致spring的bean二次代理问题,那么是不是我们配置文件也出现问题,所以导致了出现了多个ProxyCreator,出现了冲突,让controller层使用的是jdk代理,所以最后出现上面IllegalStateException异常呢?
从上面druid相关配置,我们可以看到<aop:config><aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut"/>
</aop:config>这句配置,从之前的《springAOP应尽量避免自己创建AutoProxyCreator》就可以知道,这句代码是会注册一个AutoProxyCreator、
而shiro相关配置有一句<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>也定义了一个DefaultAdvisorAutoProxyCreator,而且因为proxy-target-class的缺省,那肯定使用的是jdk代理。
所以有可能是两个代理起了冲突,导致当我们druid想去监控controller的时候是使用jdk代理,jdk代理不能对类进行代理所以才报了llegalStateException异常。
为了验证这个说法,我们可以看下日志信息。
从日志我们可以看到AspectJAwareAdvisorAutoProxyCreator创建一个对UserController的CGBLIB代理和我们定义的DefaultAdvisorAutoProxyCreator创建一个对UserController的JDK代理,一个UserController的bean有两个不同的代理,所以起了冲突。
毫无疑问,肯定是配置文件问题导致了bean的两次代理问题。
如何解决:
我们把配置文件修改一下再看日志会发生什么不同
druid配置保存不变,shiro还是自己创建DefaultAdvisorAutoProxyCreator,只是稍微做了修改
我们这里称为shiro配置2
<!-- 开启Shiro注解 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<!-- 新增了这句代码,这句代码意思是 这个属性为true时,表示被代理的是目标类本身而不是目标类的接口,实际就是强制为CGLIB代理->
<property name="proxyTargetClass" value="true"/>
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
可以看到配置2,是多一个代码<property name="proxyTargetClass" value="true"/>。这句代码意思是 这个属性为true时,表示被代理的是目标类本身而不是目标类的接口,实际就是强制为CGLIB代理。
现在我们再来看看日志有什么变化。
把shiro的配置换成配置2后,我们调用controller层时候就没有报出异常了。其实原因也很简单因为我们对controller的代理换成CGLIB代理,那肯定不会报错了。
但是这种配置2修改方式,虽然是解决了controller的代理问题,但是其实还是不好。因为我们是通过把两个AutoProxyCreator对controller的代理都编程CGLIB,所以才没有报错。
但是这是一种治标不治本的问题,因为还是存在bean二次代理。其实问题就在于AutoProxyCreator,我们定义两个,导致bean二次代理。
真正的解决方法的就是
《springAOP应尽量避免自己创建AutoProxyCreator》这一篇文章所说的,避免自己创建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之后当然也是没有报异常啦。再看看日志文件,
现在就只有AspectJAwareAdvisorAutoProxyCreator创建一个对UserController的CGLIB代理了,就不存在二次代理的问题了,
即便,在不同spring-dao.xml和spring-shiro.xml里面同时使用了<aop:config proxy-target-class="true"></aop:config> 也对一个bean只有一个代理。
总结
配置1是自己创建AutoProxyCreator,shiro官方文档 和spring集成也是这样写。但是这种写法,就像之前两篇《spring的bean二次代理问题》和《springAOP应尽量避免自己创建AutoProxyCreator》反复在讲,对于不了解spring源码来说,是很容易中招,很容易就导致bean的两次代理问题。所以还是建议采用<aop:config>去代替这种自己创建AutoProxyCreator的方法。shiro的话可以采用配置3就好了,开涛大神的博客的配置也是这样写的。
shiro的配置的话也可以参考《spring集成shiro的配置xml》