SpringMVC的设计与实现

SpringMVC的设计与实现

​ Spring MVC是MVC模式的一种实现 , 在Spring MVC 的使用中 , 除了之前讲过的ContextLoaderListener , 还有一个比较重要的类DispatchServlet , 在web.xml中也对其进行了配置 。从名字可以看出来这是一个Servlet , 这个Servlet实现的是Sun的J2EE核心模式中的前端控制器模式(Front Controller) , 所有的请求都要经过它来处理 , 进行转发、匹配、数据处理后 , 并转由页面进行展现 , 因此可以把它当做Spring MVC视线中最为核心的部分。

​ DispatchServlet与ContextLoaderListener 有没有什么联系呢 ?

​ 两者都是Spring上下文体系中非常重要的部分 , 在完成ContextLoaderListener的初始化后 , Web容器开始初始化DispatchServlet 。DispatchServlet会建立自己的上下文来持有Spring MVC的Bean对象 , 它会将ServletContext中的根上下文作为它的双亲上下文 , 并把自己持有的上下文也进行初始化并保存到ServletContext中。

​ DispatchServlet是怎么对上下文进行初始化的呢?先看看他的类继承关系图

image.png
DispatchServlet通过继承FramewServlet和HttpServletBean而继承了HttpServlet , 通过使用Servlet API来对HTTP请求进行响应 , 成为Spring MVC的前端处理器 , 同时成为MVC模块与Web容器集成的处理前端。
image.png

​ 从上图中可以看到Dispatchservlet的工作可以分为两部分: 1.初始化部分 , 由initServletBean()启动 , 通过initWebApplicationContext()方法最终调用DispatchServlet的initStrategies()方法 , 这部分做的就是上下文的初始化部分。2.对HTTP请求进行响应 , doService()方法会调用doDispatch()方法 , 在这个方法中进行了转发的操作。

1.Dispatchservlet的启动和初始化
​ 作为Servlet , 初始化时init()方法会被调用 , init方法中调用了子类的initServletBean()方法


public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // 获取Servlet的初始化参数,对Bean属性进行配置
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }
// 调用子类的initServletBean进行具体的初始化
    initServletBean();
    if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }



protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            //初始化上下文
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " ms");
        }
    }
protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // 创建上下文的过程和IoC很相似 , 没有指定就创建默认的上下文
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

DispatchServlet持有自己的一个Servlet名称命名的IoC容器 , 这个IoC容器是一个WebApplicationContext对象 , SpringMVC的具体实现和Spring应用的实现并没有什么太大差别 . 但是MVC在对容器初始化完成之后会调用initStrategies()方法,来实现转发功能的初始化 , 可以从名称看出来 ,这个方法中初始化了支持request映射的HandlerMapping、视图、国际化的localResolver等 .

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }   

initHandlerMappings方法并不是干初始化Mapping的工作的 , 它只是初始化handlerMappings属性的值 , 决定后续的转发是由哪个子类执行的

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // 默认加载的是DispatcherServlet.properties文件中的内容
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

​ DispatcherServlet.properties

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

至于Controller是什么时候转换成Mapping的 , 可以研究一下AbstractHandlerMethodMapping类 , 从名字可以看出来该类是将method作为handler来使用的

// @since 3.1  Spring3.1之后才出现,这个时候注解驱动也出来了
// 实现了initializingBean接口,其实主要的注册操作则是通过afterPropertiesSet()接口方法来调用的
// 它是带有泛型T的。
// T:包含HandlerMethod与传入请求匹配所需条件的handlerMethod的映射~
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    // SCOPED_TARGET的BeanName的前缀
    private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
    private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH = new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
    // 跨域相关
    private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
    static {
        ALLOW_CORS_CONFIG.addAllowedOrigin("*");
        ALLOW_CORS_CONFIG.addAllowedMethod("*");
        ALLOW_CORS_CONFIG.addAllowedHeader("*");
        ALLOW_CORS_CONFIG.setAllowCredentials(true);
    }
    
    // 默认不会去祖先容器里面找Handlers
    private boolean detectHandlerMethodsInAncestorContexts = false;
    // @since 4.1提供的新接口
    // 为处HandlerMetho的映射分配名称的策略接口   只有一个方法getName()
    // 唯一实现为:RequestMappingInfoHandlerMethodMappingNamingStrategy
    // 策略为:@RequestMapping指定了name属性,那就以指定的为准  否则策略为:取出Controller所有的`大写字母` + # + method.getName()
    // 如:AppoloController#match方法  最终的name为:AC#match 
    // 当然这个你也可以自己实现这个接口,然后set进来即可(只是一般没啥必要这么去干~~)
    @Nullable
    private HandlerMethodMappingNamingStrategy<T> namingStrategy;
    // 内部类:负责注册~
    private final MappingRegistry mappingRegistry = new MappingRegistry();

    // 此处细节:使用的是读写锁  比如此处使用的是读锁   获得所有的注册进去的Handler的Map
    public Map<T, HandlerMethod> getHandlerMethods() {
        this.mappingRegistry.acquireReadLock();
        try {
            return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
        } finally {
            this.mappingRegistry.releaseReadLock();
        }
    }
    // 此处是根据mappingName来获取一个Handler  此处需要注意哦~~~
    @Nullable
    public List<HandlerMethod> getHandlerMethodsForMappingName(String mappingName) {
        return this.mappingRegistry.getHandlerMethodsByMappingName(mappingName);
    }
    // 最终都是委托给mappingRegistry去做了注册的工作   此处日志级别为trace级别
    public void registerMapping(T mapping, Object handler, Method method) {
        if (logger.isTraceEnabled()) {
            logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
        }
        this.mappingRegistry.register(mapping, handler, method);
    }
    public void unregisterMapping(T mapping) {
        if (logger.isTraceEnabled()) {
            logger.trace("Unregister mapping \"" + mapping + "\"");
        }
        this.mappingRegistry.unregister(mapping);
    }

    // 这个很重要,是初始化HandlerMethods的入口~~~~~
    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }
    // 看initHandlerMethods(),观察是如何实现加载HandlerMethod
    protected void initHandlerMethods() {
        // getCandidateBeanNames:Object.class相当于拿到当前容器(一般都是当前容器) 内所有的Bean定义信息
        // 如果阁下容器隔离到到的话,这里一般只会拿到@Controller标注的web组件  以及其它相关web组件的  不会非常的多的~~~~
        for (String beanName : getCandidateBeanNames()) {
            // BeanName不是以这个打头得  这里才会process这个BeanName~~~~
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                // 会在每个Bean里面找处理方法,HandlerMethod,然后注册进去
                processCandidateBean(beanName);
            }
        }
        // 略:它就是输出一句日志:debug日志或者trace日志   `7 mappings in 'requestMappingHandlerMapping'`
        handlerMethodsInitialized(getHandlerMethods());
    }

    // 确定指定的候选bean的类型,如果标识为Handler类型,则调用DetectHandlerMethods
    // isHandler(beanType):判断这个type是否为Handler类型   它是个抽象方法,由子类去决定到底啥才叫Handler~~~~
    // `RequestMappingHandlerMapping`的判断依据为:该类上标注了@Controller注解或者@Controller注解  就算作是一个Handler
    // 所以此处:@Controller起到了一个特殊的作用,不能等价于@Component的哟~~~~
    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            beanType = obtainApplicationContext().getType(beanName);
        } catch (Throwable ex) {
            // 即使抛出异常  程序也不会终止~
        }
        if (beanType != null && isHandler(beanType)) {
            // 这个和我们上篇博文讲述的类似,都属于detect探测系列~~~~
            detectHandlerMethods(beanName);
        }
    }

    // 在指定的Handler的bean中查找处理程序方法Methods  找打就注册进去:mappingRegistry
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
        
            // 又是非常熟悉的方法:MethodIntrospector.selectMethods
            // 它在我们招@EventListener、@Scheduled等注解方法时已经遇到过多次
            // 此处特别之处在于:getMappingForMethod属于一个抽象方法,由子类去决定它的寻找规则~~~~  什么才算作一个处理器方法
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        } catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
                        }
                    });
            
            // 把找到的Method  一个个遍历,注册进去~~~~
            methods.forEach((method, mapping) -> {
                // 找到这个可调用的方法(AopUtils.selectInvocableMethod)
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }
}

大致步骤: 找到容器中的所有bean , 如果bean是handler的话就就对bean进行解析 (是否是handler是交给子类去实现的) , 如果是handler获取到该bean中的所有method , (以RequestMappingHandlerMapping为例 ),如果方法上面有requestMapping,根据注解的信息生成RequestMappingInfo , 并且将方法上的信息与类上面的信息结合 , 将方法与mapping信息的对应关系存到Map中 ,遍历Map为method找到对应的可调用方法invocableMethod , 然后将方法注册到HandlerMapping中 , 注册存储的地方是AbstractHandlerMethodMapping.MappingRegistry:内部类注册中心

class MappingRegistry {
        // mapping对应的其MappingRegistration对象~~~
        private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
        // 保存着mapping和HandlerMethod的对应关系~
        private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
        // 保存着URL与匹配条件(mapping)的对应关系  当然这里的URL是pattern式的,可以使用通配符
        // 这里的Map不是普通的Map,而是MultiValueMap,它是个多值Map。其实它的value是一个list类型的值
        // 至于为何是多值?有这么一种情况  URL都是/api/v1/hello  但是有的是get post delete等方法   所以有可能是会匹配到多个MappingInfo的
        private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
        // 这个Map是Spring MVC4.1新增的(毕竟这个策略接口HandlerMethodMappingNamingStrategy在Spring4.1后才有,这里的name是它生成出来的)
        // 保存着name和HandlerMethod的对应关系(一个name可以有多个HandlerMethod)
        private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
        
        // 这两个就不用解释了
        private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
        // 读写锁~~~ 读写分离  提高启动效率
        private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        ... // 提供一些查找方法,都不是线程安全的
        
        // 读锁提供给外部访问,写锁自己放在内部即可~~~
        public void acquireReadLock() {
            this.readWriteLock.readLock().lock();
        }
        public void releaseReadLock() {
            this.readWriteLock.readLock().unlock();
        }

        // 注册Mapping和handler 以及Method    此处上写锁保证线程安全~
        public void register(T mapping, Object handler, Method method) {
            this.readWriteLock.writeLock().lock();
            try {
                // 此处注意:都是new HandlerMethod()了一个新的出来~~~~
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                // 同样的:一个URL Mapping只能对应一个Handler
                // 这里可能会出现常见的一个异常信息:Ambiguous mapping. Cannot map XXX 
                assertUniqueMethodMapping(handlerMethod, mapping);
        
                // 缓存Mapping和handlerMethod的关系  
                this.mappingLookup.put(mapping, handlerMethod);

                // 保存url和RequestMappingInfo(mapping)对应关系
                // 这里注意:多个url可能对应着同一个mappingInfo呢~  毕竟@RequestMapping的url是可以写多个的~~~~
                List<String> directUrls = getDirectUrls(mapping);
                for (String url : directUrls) {
                    this.urlLookup.add(url, mapping);
                }

                // 保存name和handlerMethod的关系  同样也是一对多
                String name = null;
                if (getNamingStrategy() != null) {
                    name = getNamingStrategy().getName(handlerMethod, mapping);
                    addMappingName(name, handlerMethod);
                }

                CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
                if (corsConfig != null) {
                    this.corsLookup.put(handlerMethod, corsConfig);
                }

                // 注册mapping和MappingRegistration的关系
                this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
            }
            // 释放锁
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }

        // 相当于进行一次逆向操作~
        public void unregister(T mapping) { ... }
        ...
    }

参考:https://cloud.tencent.com/developer/article/1497621

注册完成之后DispatchServlet的启动过程中的重要部分就结束了 ,下面看一下调用过程

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

DispatchServlet也是一个servlet ,我们知道servlet的调用方法入口是doService()方法,在doService()方法中又调用了dodispatch()方法,在这个这个方法中又调用了getHandler()方法 , 根据请求的路径找到对应的handler , 这个方法是请求分发过程中比较重要的部分

DispatchServlet类:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping hm : this.handlerMappings) {
                if (logger.isTraceEnabled()) {
                    logger.trace(
                            "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
                }
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

AbstractHandlerMapping类:

获取到handle并且把拦截器封装到HandlerExecutionChain中 , 以便后面使用

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    ...
    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 要进行匹配的  请求的URI path
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        this.mappingRegistry.acquireReadLock();
        try {
            //委托给方法lookupHandlerMethod() 去找到一个HandlerMethod去最终处理~
            HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
            return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
        }
        finally {
            this.mappingRegistry.releaseReadLock();
        }
    }
    @Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        // Match是一个private class,内部就两个属性:T mapping和HandlerMethod handlerMethod
        List<Match> matches = new ArrayList<>();
        
        // 根据lookupPath去注册中心里查找mappingInfos,因为一个具体的url可能匹配上多个MappingInfo的
        // 至于为何是多值?有这么一种情况  URL都是/api/v1/hello  但是有的是get post delete等方法  当然还有可能是headers/consumes等等不一样,都算多个的  所以有可能是会匹配到多个MappingInfo的
        // 所有这个里可以匹配出多个出来。比如/hello 匹配出GET、POST、PUT都成,所以size可以为3
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        
        if (directPathMatches != null) {
            // 依赖于子类实现的抽象方法:getMatchingMapping()  看看到底匹不匹配,而不仅仅是URL匹配就行
            // 比如还有method、headers、consumes等等这些不同都代表着不同的MappingInfo的
            // 最终匹配上的,会new Match()放进matches里面去
            addMatchingMappings(directPathMatches, matches, request);
        }
    
        // 当还没有匹配上的时候,别无选择,只能浏览所有映射
        // 这里为何要浏览所有的mappings呢?而不是报错404呢?这里我有点迷糊,愿有知道的指明这个设计意图~~~
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }

        // 单反只要找到了一个匹配的  就进来这里了~~~
        // 请注意:因为到这里   匹配上的可能还不止一个  所以才需要继续处理~~
        if (!matches.isEmpty()) {
            // getMappingComparator这个方法也是抽象方法由子类去实现的。
            // 比如:`RequestMappingInfoHandlerMapping`的实现为先比较Method,patterns、params
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            // 排序后的最佳匹配为get(0)
            Match bestMatch = matches.get(0);
    
            // 如果总的匹配个数大于1的话
            if (matches.size() > 1) {
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
        
                // 次最佳匹配
                Match secondBestMatch = matches.get(1);
                // 如果发现次最佳匹配和最佳匹配  比较是相等的  那就报错吧~~~~
                // Ambiguous handler methods mapped for~~~
                // 注意:这个是运行时的检查,在启动的时候是检查不出来的~~~  所以运行期的这个检查也是很有必要的~~~   否则就会出现意想不到的效果
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException(
                            "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
            // 把最最佳匹配的方法  放进request的属性里面~~~
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            // 它也是做了一件事:request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath)
            handleMatch(bestMatch.mapping, lookupPath, request);
            // 最终返回的是HandlerMethod~~~
            return bestMatch.handlerMethod;
        }
        // 一个都没匹配上,handleNoMatch这个方法虽然不是抽象方法,protected方法子类复写
        // RequestMappingInfoHandlerMapping有复写此方法~~~~
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }
    ...

    // 因为上面说了mappings可能会有多个,比如get post put的都算~~~这里就是要进行筛选出所有match上的
    private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
        for (T mapping : mappings) {
            // 只有RequestMappingInfoHandlerMapping 实现了一句话:return info.getMatchingCondition(request);
            // 因此RequestMappingInfo#getMatchingCondition() 方法里大有文章可为~~~
            // 它会对所有的methods、params、headers... 都进行匹配  但凡匹配不上的就返回null  
            T match = getMatchingMapping(mapping, request);
            if (match != null) {
                matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
            }
        }
    }
}

// ===============RequestMappingInfo 的源码部分讲解================
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
    
    // 这些个匹配器都继承自AbstractRequestCondition,会进行各自的匹配工作  
    // 下面会以PatternsRequestCondition为例进行示例讲解~~~~~
    // 他们顶级抽象接口为:RequestCondition  @since 3.1 :Contract for request mapping conditions
    private final PatternsRequestCondition patternsCondition;
    private final RequestMethodsRequestCondition methodsCondition;
    private final ParamsRequestCondition paramsCondition;
    private final HeadersRequestCondition headersCondition;
    private final ConsumesRequestCondition consumesCondition;
    private final ProducesRequestCondition producesCondition;
    private final RequestConditionHolder customConditionHolder;

    // 因为类上和方法上都可能会有@RequestMapping注解,所以这里是把语意思合并  该方法来自顶层接口
    @Override
    public RequestMappingInfo combine(RequestMappingInfo other) {
        String name = combineNames(other);
        PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
        RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
        ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
        HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
        ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
        ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
        RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);

        return new RequestMappingInfo(name, patterns,
                methods, params, headers, consumes, produces, custom.getCondition());
    }

    // 合并后,就开始发挥作用了,该接口来自于顶层接口~~~~
    @Override
    @Nullable
    public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
        RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
        if (methods == null) {
            return null;
        }
        ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
        if (params == null) {
            return null;
        }
        HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
        if (headers == null) {
            return null;
        }
        ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
        if (consumes == null) {
            return null;
        }
        ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
        if (produces == null) {
            return null;
        }
        PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
        if (patterns == null) {
            return null;
        }
        RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
        if (custom == null) {
            return null;
        }

        return new RequestMappingInfo(this.name, patterns,
                methods, params, headers, consumes, produces, custom.getCondition());
    }
}
​   找到对应的handler之后根据handler找对应的HandlerAdapter, 我们使用的requestMapping对应的adapter是RequestMappingHandlerAdapter

​   执行所有注册拦截器的preHandler方法, 该方法中调用所有拦截器的preHandle方法,如果preHandle返回true继续执行下一个拦截器 ,否则调用triggerAfterCompletion方法 , triggerAfterCompletion会倒叙调用已经执行过得拦截器的afterCompletion的方法。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
            throws Exception {

        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = this.interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                }
                catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }
    }

可以看出,以上方法就是遍历处理入参,但是可以从这个代码中引出一个概念叫做:HandlerMethodArgumentResolver,HandlerMethod的参数解析器。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350