Spring源碼從入門到放棄-Controller註冊

Spring源碼從入門到放棄-Controller註冊

{toc}java

@contact:zhangxin@benmu-health.com
@update:2017-03-23 02:18:31
@spirng.version:4.3.7.RELEASEweb

本文主要介紹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的一個方法。跨域

1.RequestMappingHandlerMapping

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

2.afterPropertiesSet

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);
                }
        }
    
    }

3.解析RequestMappingInfo

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);
        }

4.RequestMappingInfo與handler註冊

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();
            }
        }

5.DispatcherServlet與MappingRegistry

這裏順帶提一下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);
        }
    }
相關文章
相關標籤/搜索