在springmvc+shiro项目中使用druid监听controller层出现IllegalStateException异常

最近在做个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的CGLIB代理.png

我们定义的DefaultAdvisorAutoProxyCreator创建一个对UserController的JDK代理.png

从日志我们可以看到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代理。

现在我们再来看看日志有什么变化。

AspectJAwareAdvisorAutoProxyCreator创建一个对UserController的CGLIB代理.png

我们定义的DefaultAdvisorAutoProxyCreator创建一个对UserController的CGLIB代理.png

把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代理.png

现在就只有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》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,732评论 6 342
  • 什么是Spring Spring是一个开源的Java EE开发框架。Spring框架的核心功能可以应用在任何Jav...
    jemmm阅读 16,438评论 1 133
  • 我喜欢一本正经的瞎搞 我更喜欢情不自禁的胡闹
    爱嗨爱嗨呦阅读 190评论 0 0
  • #作业#“每个人的行为背后都有其隐性动机,每一个行为都是其现有认知能力下对自己利益最大化的考量。从随处可见...
    张宝英阅读 211评论 1 1