SpringMVC 篇(一)DispatcherServlet 初始化

引言

SpringMVC 的出现大大方便了 Java Web 领域的开发,开发一个 Web 接口几个注解就完事了。而 SpringBoot 的出现又进一步提升了我们的开发效率。在这一层层背后,你可曾想过它们到底是如何实现的?我相信肯定是有的,但是面对它庞大的代码体系,让人望而却步。不过不要紧,我来带大家进入 SpringBoot 的源码世界,见证一下这个 “艺术品” 是如何跑起来的。

DispatcherServlet

通过名字我们可以看出它就是一个 Servlet,而恰恰这个 Servlet 就是 SpringMVC 的核心。我们从它开始入手,抽丝剥茧,看看它到底是怎么对请求进行加工处理的。

  • 继承链

image.png
  • 启动

DispatcherServlet

/**
 * This implementation calls {@link #initStrategies}.
 */ 
 @Override
 protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
 }

这个方法是我们首先要关注的方法,因为它完成了 DispatcherServlet 相关资源的初始化,看它调用的方法 initStrategies(context); 也能看出端倪。我们先不着急往下看,我们想一想谁调用的 onRefresh() 方法呢?我们通过它的注解发现它是一个继承下来的方法,好,我们往它的上游看。

FrameworkServlet

/**
 * Callback that receives refresh events from this servlet's WebApplicationContext.
 * <p>The default implementation calls {@link #onRefresh},
 * triggering a refresh of this servlet's context-dependent state.
 * @param event the incoming ApplicationContext event
 */
 public void onApplicationEvent(ContextRefreshedEvent event) {
    this.refreshEventReceived = true;
    onRefresh(event.getApplicationContext());
 }
/**
 * Template method which can be overridden to add servlet-specific refresh work.
 * Called after successful context refresh.
 * <p>This implementation is empty.
 * @param context the current WebApplicationContext
 * @see #refresh()
 */
 protected void onRefresh(ApplicationContext context) {
    // For subclasses: do nothing by default.
 }

我们发现 onRefresh() 方法是个空方法,而它的调用者就在它的上边,即:onApplicationEvent(ContextRefreshedEvent event),通过名字我们可以看出它是一个事件方法,当上下文刷新的时候,该方法会被触发。但是 FrameworkServlet并没有实现任何相关的监听器,是如何完成事件监听的呢?不要紧,我们大概看看这个类的结构,看看能发现什么端倪不。我们发现它有一个内部类,而这个内部类实现了监听器,而实现的监听方法恰恰调用了onApplicationEvent(ContextRefreshedEvent event)

/**
 * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
 * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.
 */
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
}

看到这里的时候,信心满满的,但是当你启动准备验证的时候,发现,不对。好像并不是通过事件触发 onRefresh() 的。为什么?如果没有事件产生,那么该方法肯定就无法触发了。所以它肯定还有别的调用入口,我们继续往下看。

FrameworkServlet

    /**
     * Initialize and publish the WebApplicationContext for this servlet.
     * <p>Delegates to {@link #createWebApplicationContext} for actual creation
     * of the context. Can be overridden in subclasses.
     * @return the WebApplicationContext instance
     * @see #FrameworkServlet(WebApplicationContext)
     * @see #setContextClass
     * @see #setContextConfigLocation
     */
    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) {
            // No context instance is defined for this servlet -> create a local one
            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;
    }

我们把焦点放到这行代码上:

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

也就是说,如果事件没有成功发布或者发布较早,那么需要手动调用 onRefresh(wac) 方法(也间接说明了,事件并不靠谱)。大体的调用关系链可以按照如下的方式分析:

servlet.init() -> GenericServlet.init() -> HttpServletBean.init() -> HttpServletBean.initServletBean() -> FrameworkSerlvet.initServletBean() -> FrameworkSerlvet.initWebApplicationContext() -> FrameworkSerlvet.onRefresh(wac)

总结

onRefresh() 方法的调用有两个入口,一个是通过上下文事件触发,一个是手动触发(当事件触发失败的时候)。而手动触发的顶端则是 servlet 的 init() 方法。

组件初始化

当 onRefresh() 方法被触发的时候,组件的初始化工作就展开了。在开始之前,我们先重点关注一个配置文件,那就是跟 DispatcherServlet 同一级的 DispatcherServlet.properties 文件。该文件存放的就是组件的默认实现。

DispatcherServlet.properties

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

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

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

DispatcherServlet

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
 protected void initStrategies(ApplicationContext context) {
        // 文件上传解析器
        initMultipartResolver(context);
        // 本地化解析器
        initLocaleResolver(context);
        // 主题解析器
        initThemeResolver(context);
        // 处理器映射器(url 和 Controller 方法的映射)
        initHandlerMappings(context);
        // 处理器适配器(实际执行 Controller 方法)
        initHandlerAdapters(context);
        // 处理器异常解析器
        initHandlerExceptionResolvers(context);
        // RequestToViewName 解析器
        initRequestToViewNameTranslator(context);
        // 视图解析器(视图的匹配和渲染)
        initViewResolvers(context);
        // FlashMap 管理器
        initFlashMapManager(context);
 }

由于各个组件的加载完全一样,所以这里我只选择 initHandlerMappings(context) 进行详细的介绍,该方法加载 HandlerMapping.class 的实例,也就是处理 urlcontroller 的映射。

    /**
     * 实例化该类所使用的 HandlerMappings 映射器,如果该命名空间下(父子容器中的子容器)的 bean 容器中没有该 bean 的定义,那么默认会分配一个 BeanNameUrlHandlerMapping 映射器。
     */
    private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // 从当前容器及它的父容器中获取对应的 bean 对象。
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                // 根据 @Order 进行排序。
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                // 根据名字及类型取出对应的 bean 对象。
                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.
            }
        }

        // 如果没有映射器被发现,那么会从配置文件中获取出一个默认的映射器。
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isTraceEnabled()) {
                logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                        "': using default strategies from DispatcherServlet.properties");
            }
        }
    }

到这里 HandlerMapping 类型的对象已经加载完毕了,为了进行进一步说明,我们以 RequestMappingHandlerMapping 的加载为例进行详细的说明。为什么讲它呢?它就是处理 urlcontroller 映射的处理器。废话不多说,我们开始吧(细心的话,你会发现在进行检索的时候,有一个父容器的存在,这就是 Web 中的父子容器,至于什么时候加载的父容器,大家可以回想一下先前的文章)!
RequestMappingHandlerMapping 通过它的父类间接实现了 InitializingBean 接口,该接口在之前介绍容器的时候已经知道它的具体作用,所以这里我们不多说,直接进入接口方法看看:

RequestMappingHandlerMapping

    @Override
    @SuppressWarnings("deprecation")
    public void afterPropertiesSet() {
        this.config = new RequestMappingInfo.BuilderConfiguration();
        this.config.setUrlPathHelper(getUrlPathHelper());
        this.config.setPathMatcher(getPathMatcher());
        this.config.setSuffixPatternMatch(useSuffixPatternMatch());
        this.config.setTrailingSlashMatch(useTrailingSlashMatch());
        this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
        this.config.setContentNegotiationManager(getContentNegotiationManager());

        super.afterPropertiesSet();
    }

这里我们直接进入它的父方法看看:

AbstractHandlerMethodMapping<T>

    // Handler method detection

    /**
     * 初始化时检测 handler
     * @see #initHandlerMethods
     */
    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }

    /**
     * 从容器中检索出已经注册好的 handler bean。
     * @see #getCandidateBeanNames()
     * @see #processCandidateBean
     * @see #handlerMethodsInitialized
     */
    protected void initHandlerMethods() {
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }
    /**
     *  检测 object 类型的 bean,并将 bean 的名称放到数组里。
     * @since 5.1
     * @see #setDetectHandlerMethodsInAncestorContexts
     * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors
     */
    protected String[] getCandidateBeanNames() {
        return (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                obtainApplicationContext().getBeanNamesForType(Object.class));
    }

    /**
     * 这里只处理 @Controller 或 @RequestMapping 修饰的 bean
     */
     protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {
            // An unresolvable bean type, probably from a lazy bean - let's ignore it.
            if (logger.isTraceEnabled()) {
                logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
            }
        }
        // 这里就是用来筛选 @Controller 或 @RequestMapping 修饰的类
        if (beanType != null && isHandler(beanType)) {
            detectHandlerMethods(beanName);
        }
    }

    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

通过以上代码,我们可以看出,把 @Controller 或者 @RequestMapping 修饰的 bean 筛选出来,筛选出来之后做什么呢?我们继续往下看:

AbstractHandlerMethodMapping<T>

    // 这里的 handler 就是每个 controller 类的名字(并非全限定名)
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            // 这里 Method 就是具体的方法全限定名,而 T 就是该方法映射信息(HTTP 方法 + URL)
            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);
                        }
                    });
            if (logger.isTraceEnabled()) {
                logger.trace(formatMappings(userType, methods));
            }
            methods.forEach((method, mapping) -> {
                // 利用反射获取方法对象
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                // 进行注册,handler 是 controller 类的简称,invocableMethod 目标方法对象,mapping 是接口映射信息(HTTP 方法 + URL)
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

       protected void registerHandlerMethod(Object handler, Method method, T mapping) {
           this.mappingRegistry.register(mapping, handler, method);
        }
        //  注册 URL 与 Controller 方法
        public void register(T mapping, Object handler, Method method) {
            // Assert that the handler method is not a suspending one.
            if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
                throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
            }
            this.readWriteLock.writeLock().lock();
            try {
                HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                validateMethodMapping(handlerMethod, mapping);
                // 将 mapping 信息与方法对应起来
                this.mappingLookup.put(mapping, handlerMethod);

                List<String> directUrls = getDirectUrls(mapping);
                for (String url : directUrls) {
                    // 这里将 url 跟 mapping 信息对应起来
                    this.urlLookup.add(url, mapping);
                }

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

                this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
            }
            finally {
                this.readWriteLock.writeLock().unlock();
            }
        }

以上代码就是保存 URL 及对应的 Controller 方法,这里分了两个集合,一个是存放 URL 对应的 mapping 信息(包括了 HTTP 方法及 URL 信息 ),一个是存放 mapping 信息及对应的 Controller 方法。这里也用到了读写锁的写锁,大家可以分析一下为什么要上锁。


尾声

这篇文章主要讲解了 Dispatcher 的初始化过程,然后以 HandlerMapping 为例讲解了 Controller 方法的映射。接下来的文章【SpringMVC 篇(二)DispatcherServlet 请求】将会介绍请求发生的时候,Dispatcher 是如何处理请求的,以及各个消息转换器、异步处理器、拦截器之间的协作方式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容