这篇博客来自这个问题: 在SpringMVC中@RequestMapping可以配置两个相同的url路径吗。
首先,这个问题会点SpringMVC的人可能都知道答案,但是上次面试中我就回答了可以。。。可以。。Spicy Chicken!!!
参考文章: http://lgbolgger.iteye.com/blog/2105108
这个问题要从 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 讲起了。
首先,在配置文件中声明了 <mvc:annotation-driven /> 注解之后, 在 initStrategies() 方法中注册了处理的类:
1 protected void initStrategies(ApplicationContext context) { 2 initMultipartResolver(context); 3 initLocaleResolver(context); 4 initThemeResolver(context); 5 initHandlerMappings(context); 6 initHandlerAdapters(context); 7 initHandlerExceptionResolvers(context); 8 initRequestToViewNameTranslator(context); 9 initViewResolvers(context); 10 initFlashMapManager(context); 11 }
容器对 @ReqeustMapping 便签的处理的简化流程就是首先 RequestMappingHandlerMapping 类去查找有 @Controller 或 @RequestMapping 的类, 然后为含有其中一个注解的类还有类中含有 @RequestMapping 注解的方法构建 HandlerMethod 对象, 之后 RequestMappingHandlerAdapter 判断是否 support 对应的方法并执行对应的方法。
具体的过程如下:
1, RequestMappingHandlerMapping 遍历所有的 bean, 判断是否有 @Controller 或 @RequestMapping 注解
1 protected void initHandlerMethods() { 2 if (logger.isDebugEnabled()) { 3 logger.debug("Looking for request mappings in application context: " + getApplicationContext()); 4 } 5 6 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? 7 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : 8 getApplicationContext().getBeanNamesForType(Object.class)); 9 10 for (String beanName : beanNames) { 11 if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) && 12 isHandler(getApplicationContext().getType(beanName))){ 13 detectHandlerMethods(beanName); 14 } 15 } 16 handlerMethodsInitialized(getHandlerMethods()); 17 }
isHandler() 判断的方法:
1 protected boolean isHandler(Class<?> beanType) { 2 return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) || 3 (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null)); 4 }
注意方法中的判断是用 || 逻辑,说明两者当中的其中一个符合即可。
2,处理含有 @RequestMapping 的方法,遍历第一步中找到的类中的所有方法,用一个 MethodFilter 查找所有的 @ReqeustMapping 注解的方法,并为找到的 @RequestMapping 的方法构建 ReqeustMappingInfo 对象
1 /** 2 * Look for handler methods in a handler. 3 * @param handler the bean name of a handler or a handler instance 4 */ 5 protected void detectHandlerMethods(final Object handler) { 6 Class<?> handlerType = 7 (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); 8 9 // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances 10 final Map<Method, T> mappings = new IdentityHashMap<Method, T>(); 11 final Class<?> userType = ClassUtils.getUserClass(handlerType); 12 13 Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { 14 @Override 15 public boolean matches(Method method) { 16 T mapping = getMappingForMethod(method, userType); 17 if (mapping != null) { 18 mappings.put(method, mapping); 19 return true; 20 } 21 else { 22 return false; 23 } 24 } 25 }); 26 27 for (Method method : methods) { 28 registerHandlerMethod(handler, method, mappings.get(method)); 29 } 30 }
其中查找 @RequestMapping 注解的方法的 getMappingForMethod() 方法:
1 /** 2 * Uses method and type-level @{@link RequestMapping} annotations to create 3 * the RequestMappingInfo. 4 * @return the created RequestMappingInfo, or {@code null} if the method 5 * does not have a {@code @RequestMapping} annotation. 6 * @see #getCustomMethodCondition(Method) 7 * @see #getCustomTypeCondition(Class) 8 */ 9 @Override 10 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { 11 RequestMappingInfo info = null; 12 RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class); 13 if (methodAnnotation != null) { 14 RequestCondition<?> methodCondition = getCustomMethodCondition(method); 15 info = createRequestMappingInfo(methodAnnotation, methodCondition); 16 RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); 17 if (typeAnnotation != null) { 18 RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType); 19 info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info); 20 } 21 } 22 return info; 23 }
构建 RequestMappingInfo 对象的 createRequestMappingInfo() 方法:
1 /** 2 * Created a RequestMappingInfo from a RequestMapping annotation. 3 */ 4 protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) { 5 String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value()); 6 return new RequestMappingInfo( 7 new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), 8 this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions), 9 new RequestMethodsRequestCondition(annotation.method()), 10 new ParamsRequestCondition(annotation.params()), 11 new HeadersRequestCondition(annotation.headers()), 12 new ConsumesRequestCondition(annotation.consumes(), annotation.headers()), 13 new ProducesRequestCondition(annotation.produces(), annotation.headers(), this.contentNegotiationManager), 14 customCondition); 15 }
3,构建完 RequestMappingInfo 对象后, 存储到 Map 类型的 handlerMethods 对象中
1 /** 2 * Register a handler method and its unique mapping. 3 * @param handler the bean name of the handler or the handler instance 4 * @param method the method to register 5 * @param mapping the mapping conditions associated with the handler method 6 * @throws IllegalStateException if another method was already registered 7 * under the same mapping 8 */ 9 protected void registerHandlerMethod(Object handler, Method method, T mapping) { 10 HandlerMethod newHandlerMethod = createHandlerMethod(handler, method); 11 HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping); 12 if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) { 13 throw new IllegalStateException("Ambiguous mapping found. Cannot map ‘" + newHandlerMethod.getBean() + 14 "‘ bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already ‘" + 15 oldHandlerMethod.getBean() + "‘ bean method\n" + oldHandlerMethod + " mapped."); 16 } 17 18 this.handlerMethods.put(mapping, newHandlerMethod); 19 if (logger.isInfoEnabled()) { 20 logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod); 21 } 22 23 Set<String> patterns = getMappingPathPatterns(mapping); 24 for (String pattern : patterns) { 25 if (!getPathMatcher().isPattern(pattern)) { 26 this.urlMap.add(pattern, mapping); 27 } 28 } 29 }
***在这个地方, 给出了文章开头的问题的答案, 如果存在 @RequestMapping 注解有相同的访问路径时,会在此处抛出异常 (注释很重要)*************
4, 构建完了 private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap(); 这个对象之后,该对象就包含了所有的它应该包含的 bean 了,它的key为RequestMappingInfo对象,value为handler和它中含有@RequestMapping注释的方法method构建的HandlerMethod。
5, url 匹配路径时, RequestMappingHandlerAdapter 依据为是否是HandlerMethod 类型判断是否执行该方法。(这点我不太懂)
1 public final boolean supports(Object handler) { 2 return handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler); 3 }
最后,针对文章开头的问题, 对第三步步骤截图做一个简单的示例。
声明两个相同路径的 @RequestMapping 注解并查看错误:
查看错误:
正好是步骤三中抛出异常的位置,问题结束!!
一直用 IntelliJ 查看 Spring 的源码, 今天用 Maven 工程,查看源码的时候直接可以打开源码的文件,不用再 attach Source 了,而且发现 eclipse 里面的源码还带有英文的注释, IntelliJ 反编译后的源码文件都不带注释的, 以后的源码查看可能要改改了, 算是新发现吧。这个问题就到这了( ̄▽ ̄)~*