首页 > 代码库 > SpringMVC在@RequestMapping配置两个相同路径

SpringMVC在@RequestMapping配置两个相同路径

  这篇博客来自这个问题: 在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 反编译后的源码文件都不带注释的, 以后的源码查看可能要改改了, 算是新发现吧。这个问题就到这了( ̄▽ ̄)~*

 

SpringMVC在@RequestMapping配置两个相同路径