{toc}java
@contact:zhangxin@benmu-health.com
@update:2017-03-23 02:18:31
@spirng.version:4.3.7.RELEASE
web
本文主要介紹SpringMVC中如何註冊Controllerspring
SpringMVC中Controller由@Controller和@RequestMapping註解定義,@Controller定義對象爲一個Controller,@RequestMapping定義了請求url路徑,SpringMVC內部將Controller的方法抽象爲多個org.springframework.web.method.HandlerMethod
,將Method的@RequestMapping註解抽象成org.springframework.web.servlet.mvc.method.RequestMappingInfo
,一個到來的http請求,通過DispatcherServlet轉發,經過RequestMappingInfo匹配路徑,找到對應的HandlerMethod處理請求。這裏的HandlerMethod能夠理解爲Controller的一個方法。跨域
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
類的初始化過程完成了對@Controller和@RequestMapping兩個註解的解析該類由spring容器初始化過程解析。解析<mvc:annotation-driven />
標籤時會自動向spring容器註冊該類。並在DispatcherServlet初始化的時候,在initHandlerMappings()
方法中會從Spring容器中將該HandlerMapping做爲DispatcherServlet的成員,用以處理http請求。mvc
繼承關係:
app
該類實現了HandlerMapping和InitializingBean兩個接口,初始化方法afterPropertiesSet()
完成了對@Controller和@RequestMapping的解析和註冊。cors
Controller註冊是在初始化方法afterPropertiesSet中,首先拿到Spring容器中全部的Bean,對每個Bean判斷是否爲Controlleride
@Override public void afterPropertiesSet() { initHandlerMethods(); } /** * Scan beans in the ApplicationContext, detect and register handler methods. * @see #isHandler(Class) * @see #getMappingForMethod(Method, Class) * @see #handlerMethodsInitialized(Map) */ protected void initHandlerMethods() { //拿到全部bean的名字 String[] beanNames = getApplicationContext().getBeanNamesForType(Object.class)); for (String beanName : beanNames) { Class<?> beanType = null; //拿到Class beanType = getApplicationContext().getType(beanName); //若是改bean帶有@Controller和@RequestMapping註解 if (beanType != null && isHandler(beanType)) { //註冊hanler mapping即Controller detectHandlerMethods(beanName); } } }
detectHandlerMethods
完成對一個Controller的解析,將@RequestMapping方法解析成映射和可執行的HandlerMethod,映射抽象爲RequestMappingInfo(即url pattern),將可執行的HandlerMethod和RequestMappingInfo一塊兒註冊到MappingRegistry中,DispatcherServlet收到一個請求的時候會從MappingRegistry中取出與url匹配的handler method來執行。ui
protected void detectHandlerMethods(final Object handler) { Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); //拿到用戶實際註冊的類,防止CGLIB代理 final Class<?> userType = ClassUtils.getUserClass(handlerType); //選出該類打@RequestMapping的方法,並轉成Map<Method,RequestMappingInfo> Map<Method, T> methods = MethodIntrospector.selectMethods(userType, new MethodIntrospector.MetadataLookup<T>() { @Override public T inspect(Method method) { //對每個method,轉成RequestMappingInfo,若是不帶@RequestMapping註解則返回null return getMappingForMethod(method, userType); } }); for (Map.Entry<Method, T> entry : methods.entrySet()) { //包裝成一個可執行方法 Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType); //實際爲RequestMappingInfo T mapping = entry.getValue(); //將RequestMappingInfo和handler註冊到MappingRegistry //DispatcherServlet收到一個請求的時候會從MappingRegistry中取出與url匹配的handler來執行 registerHandlerMethod(handler, invocableMethod, mapping); } }
getMappingForMethod()
方法中完成了將帶有@RequestMapping註解的方法轉爲RequestMappingInfo。
分別將Class和Method上的@RequestMapping拿到,用屬性生成RequestMappingInfo。而後將兩個RequestMappingInfo合併成一個。e.g. Class上的註解爲path=/test,Method上的註解爲path=/hello,method=POST,合併以後就是path=/test/hello,method=POST,而且爲每個RequestMappingInfo生成一個PatternsRequestCondition,用來完成DispatchServlet分發請求時url匹配。this
@Override protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { //解析method的@RequestMapping RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { //解析Class的@RequestMapping RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { //將兩個@RequestMapping合併 info = typeInfo.combine(info); } } return info; } private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { //拿到註解 RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); return (requestMapping != null ? createRequestMappingInfo(requestMapping, null) : null); } protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, RequestCondition<?> customCondition) { //用@RequestMapping的屬性生成RequestMappingInfo return RequestMappingInfo .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))//e.g. /test .methods(requestMapping.method())//e.g. POST .params(requestMapping.params()) .headers(requestMapping.headers()) .consumes(requestMapping.consumes()) .produces(requestMapping.produces()) .mappingName(requestMapping.name()) .customCondition(customCondition) .options(this.config) .build(); } @Override public RequestMappingInfo build() { ContentNegotiationManager manager = this.options.getContentNegotiationManager(); //生成路徑匹配類,DispatcherServlet中分發url請求時調用 PatternsRequestCondition patternsCondition = new PatternsRequestCondition( this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(), this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(), this.options.getFileExtensions()); return new RequestMappingInfo(this.mappingName, patternsCondition, new RequestMethodsRequestCondition(methods), new ParamsRequestCondition(this.params), new HeadersRequestCondition(this.headers), new ConsumesRequestCondition(this.consumes, this.headers), new ProducesRequestCondition(this.produces, this.headers, manager), this.customCondition); }
handler最終會被封裝成HandlerMethod
,
RequestMappingInfo與HandlerMethod都註冊到org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry
中,MappingRegistry有兩個屬性,Map<RequestMappingInfo, HandlerMethod>
和Map<url, HandlerMethod>
,維護了路徑和HandlerMethod的關係。註冊@Controller即生成這兩個Map。
protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); } public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { //經過Bean和Method,抽象成可執行的HandlerMethod,即Controller的帶有@RequestMapping註解的Method HandlerMethod handlerMethod = createHandlerMethod(handler, method); //註冊Map<RequestMappingInfo, HandlerMethod> this.mappingLookup.put(mapping, handlerMethod); List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { //註冊Map<url, HandlerMethod> this.urlLookup.add(url, mapping); } //http跨域配置 CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } //註冊Map<RequestMappingInfo, MappingRegistration<RequestMappingInfo>> this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); } }
這裏順帶提一下DispatcherServlet如何找處處理當前Http Request的HandlerMethod,最終http請求由匹配到的HandlerMethod來處理。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); //從Map<url, HandlerMethod>中找 List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { //從全部的RequestMappingInfo中找 addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); } if (!matches.isEmpty()) { //本質爲Comparator<RequestMappingInfo> Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); Collections.sort(matches, comparator); //選出最匹配當前Request的RequestMappingInfo Match bestMatch = matches.get(0); if (matches.size() > 1) { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { //校驗 Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); } } //處理url上的template variables, matrix variables handleMatch(bestMatch.mapping, lookupPath, request); //拿到handlerMethod return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }