這篇博客來自這個問題: 在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 反編譯后的源碼文件都不帶注釋的, 以后的源碼查看可能要改改了, 算是新發現吧。這個問題就到這了( ̄▽ ̄)~*