问题提出
在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
中,这个类比较长,但关键脉络是以下几步:
- 获取type的annotations(element.getDeclaredAnnotations());
- 如果可以获取到,直接解析并返回;
- 如果没有,则分析该type是否为method(因为现在这个case是类,所以这块不展开);
- 如果没有,则分析该type是否为class,如果是的话
- 获取该type的接口(多个),递归的执行该过程;
- 如果没有,获取该type的超类,递归的执行该过程;
- 如果全部没有找到,返回null(可等价理解isHandler返回了false)
由于加载了@RequirePermissions
后,这个type最终返回了#7,也就没法将该类的所有path写入“路由表”了。那么问题就变成了这个type不是我事先定义的Controller了吗?确实是这样的。
问题追踪2:Type变成了什么
通过以上分析,发现这个CategoryController
类在获取type时确实是变了,那么变成什么了呢?变成了com.sun.proxy.$Proxy69
,也就是一个代理类,这个代理类在#2,#5(向上递归),#6(向上递归)都没有发现annotation,所以返回了#7。从CategoryController
变成Proxy
的过程如下
- 在生成这个bean的时候,调用AbstractAutowireCapableBeanFactory类的applyBeanPostProcessorsAfterInitialization方法,这里先获得了bean处理的processor(有19个之多),然后逐一调用每个processor的postProcessAfterInitialization方法;
-
在这个processor中,创建了一个proxy
processor列表 - 这个processor执行后,type从Controller变成了CategoryController$$EnhancerBySpringCGLIB$$389b723@5910;
- 这还没完,注意上图的第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的配置变了
再次跟一遍代码,发现在isHandler方法的步骤中,在#6会获取代理的超类,就是Controller类,当然也就获得了Spring的官方注解RequestMapping
,补图一张
至此问题解决。
网友评论