美文网首页Spring Boot
Shiro和SpringMVC整合时的一个问题研究

Shiro和SpringMVC整合时的一个问题研究

作者: 扁圆柱体 | 来源:发表于2017-11-11 15:36 被阅读765次

    问题提出

    在SpringMVC项目集成Shiro时,发现了一个比较奇怪的问题:当Controller类使用Shiro的@RequirePermissions注解时,Controller上原有的@RestController和@RequestMapping注解都失效了。具体代码如下

    @RestController
    @RequestMapping(value = "/api/gim/category")
    public class CategoryController extends BaseController {
    
        private static Logger LOGGER = LoggerFactory.getLogger(CategoryController.class);
    
        @RequiresPermissions("base:xxx:create") // 增加词句后,下面的@RequestMapping失效
        @RequestMapping(value = "/findByCategoryType/{categoryType}", method = RequestMethod.GET)
        public JsonResult findByCategoryType(@PathVariable Integer categoryType) {
            LOGGER.debug("获取分类信息, categoryType: [{}]", categoryType);
            List<Category> categoryList = categoryService.findByCategoryType(categoryType);
            return new JsonResult(JsonResult.SUCCESSFUL, categoryList);
        }
    

    这个问题在网上有一模一样的情况爆出,但无一有解,如下面的一个。
    http://www.myexception.cn/java-web/2027678.html
    http://bbs.csdn.net/topics/390974323

    问题追踪1:“路由表”缺失

    开始以为是Shiro转向的问题,跟了很多步骤,最后定位到SpringMVC的路径匹配,发现在加了Shiro注解后,该类的所有mapping路径不会保存在内部的“路由表”中。路由表只是一个形象的说法,意即在外部调用某path时,在SpingMVC的内部无法在其路由表中找到该path,所以无法映射到对应的Controller的类上,所以就报出了404的错误。

    是否加到“路由表”的判断在AbstractHandlerMethodMapping类中的isHandler方法(L213),参数为当前类的类型(classType)。这是一个抽象方法,具体实现在RequestMappingHandlerMapping类中

    @Override
        protected boolean isHandler(Class<?> beanType) {
            return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                    AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
        }
    

    这个方法从字面上可以分析为检测当前type是否包含Controller或者RequestMapping这两个注解类,其底层实际代码在AnnotatedElementUtils.searchWithFindSemantics - L1115中,这个类比较长,但关键脉络是以下几步:

    1. 获取type的annotations(element.getDeclaredAnnotations());
    2. 如果可以获取到,直接解析并返回;
    3. 如果没有,则分析该type是否为method(因为现在这个case是类,所以这块不展开);
    4. 如果没有,则分析该type是否为class,如果是的话
    5. 获取该type的接口(多个),递归的执行该过程;
    6. 如果没有,获取该type的超类,递归的执行该过程;
    7. 如果全部没有找到,返回null(可等价理解isHandler返回了false)

    由于加载了@RequirePermissions后,这个type最终返回了#7,也就没法将该类的所有path写入“路由表”了。那么问题就变成了这个type不是我事先定义的Controller了吗?确实是这样的。

    问题追踪2:Type变成了什么

    通过以上分析,发现这个CategoryController类在获取type时确实是变了,那么变成什么了呢?变成了com.sun.proxy.$Proxy69,也就是一个代理类,这个代理类在#2,#5(向上递归),#6(向上递归)都没有发现annotation,所以返回了#7。从CategoryController变成Proxy的过程如下

    1. 在生成这个bean的时候,调用AbstractAutowireCapableBeanFactory类的applyBeanPostProcessorsAfterInitialization方法,这里先获得了bean处理的processor(有19个之多),然后逐一调用每个processor的postProcessAfterInitialization方法;
    2. 在这个processor中,创建了一个proxy


      processor列表
    3. 这个processor执行后,type从Controller变成了CategoryController$$EnhancerBySpringCGLIB$$389b723@5910;
    4. 这还没完,注意上图的第7号processor,再次创建Proxy,则type又变成了$Proxy69@6131

    以上这个Proxy69就是问题追踪1中的Type了,所以导致没有加入路由表。但为什么二次代理后就不能找到在类本身/接口/超类中找到原来的Controller,进而获取到annotation呢?难道问题出在二次代理上(总感觉二次代理怪怪的。。。)

    从上面的processor列表还能看到一个问题,就是第6号和第7号两个processor做了代理,而后者就是在spring-shiro中定义的一个bean,如下


    Shiro官方指南,红框为第7号代理

    至此为止,尽管代码跟了很深了,但还是陷入了困境,因为不知道是否代理有问题,即使知道了问题,也不知道怎么修改,还是继续求助百度吧。试着用DefaultAdvisorAutoProxyCreator搜索,居然搜到开涛大神的这篇文章
    请不要再使用低级别的AOP API

    文章居然讲到了如何避免二次代理,但原理分析部分很难,差点放弃,但拖到最后,居然发现了大神建议对Shiro的上面的配置做修改,以避免二次代理。做法如下

    <aop:config proxy-target-class="true"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
    

    用aop:config配置替换掉DefaultAdvisorAutoProxyCreator的显式定义。修改后,processor列表果然不再有这个processor,且第6号processor的配置变了

    注意对比两个processor列表

    再次跟一遍代码,发现在isHandler方法的步骤中,在#6会获取代理的超类,就是Controller类,当然也就获得了Spring的官方注解RequestMapping,补图一张

    代理的超类即Controller

    至此问题解决。

    结论

    在SpringMVC环境,不要使用Shiro官方的Annotation生效配置方式,要使用<aop:config>的配置以避免,二次代理,并由此导致的Controller的所有Path不能记录到“路由表”的问题。不能记录到“路由表”会导致该类不能在框架中作为RESTful服务被发现!

    相关文章

      网友评论

        本文标题:Shiro和SpringMVC整合时的一个问题研究

        本文链接:https://www.haomeiwen.com/subject/idlwmxtx.html