美文网首页Java
SpringMVC 篇(一)DispatcherServlet

SpringMVC 篇(一)DispatcherServlet

作者: Tubetrue01 | 来源:发表于2021-01-21 20:46 被阅读0次

    引言

    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 是如何处理请求的,以及各个消息转换器、异步处理器、拦截器之间的协作方式。

    相关文章

      网友评论

        本文标题:SpringMVC 篇(一)DispatcherServlet

        本文链接:https://www.haomeiwen.com/subject/mtwrzktx.html