zoukankan      html  css  js  c++  java
  • SpringBoot之DispatcherServlet详解及源码解析

    在使用SpringBoot之后,我们表面上已经无法直接看到DispatcherServlet的使用了。本篇文章,带大家从最初DispatcherServlet的使用开始到SpringBoot源码中DispatcherServlet的自动配置进行详解。

    DispatcherServlet简介

    DispatcherServlet是前端控制器设计模式的实现,提供了Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring Ioc容器无缝集成,从而可以获得Spring的所有好处。

    DispatcherServlet作用

    DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:

    • 文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
    • 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
    • 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
    • 通过ViewResolver解析逻辑视图名到具体视图实现;
    • 本地化解析;
    • 渲染具体的视图等;
    • 如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。

    DispatcherServlet工作流程

    image

    DispatcherServlet传统配置

    DispatcherServlet作为前置控制器,通常配置在web.xml文件中的。拦截匹配的请求,Servlet拦截匹配规则要自已定义,把拦截下来的请求,依据相应的规则分发到目标Controller来处理,是配置spring MVC的第一步。

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value> 
        </init-param>
    </servlet> 
    <servlet-mapping>
         <servlet-name>dispatcherServlet</servlet-name>
         <url-pattern>*.do</url-pattern> 
    </servlet-mapping>
    

    DospatcherServlet实际上是一个Servlet(它继承HttpServlet)。DispatcherServlet处理的请求必须在同一个web.xml文件里使用url-mapping定义映射。这是标准的J2EE servlet配置。

    在上述配置中:

    • servlet-name用来定义servlet的名称,这里是dispatcherServlet。
    • servlet-class用来定义上面定义servlet的具体实现类,这里是org.springframework.web.servlet.DispatcherServlet。
    • init-param用来定义servlet的初始化参数,这里指定要初始化WEB-INF文件夹下的dispatcherServlet-servlet.xml。如果spring-mvc.xml的命名方式是前面定义servlet-name+"-servlet",则可以不用定义这个初始化参数,(Spring默认配置文件为“/WEB-INF/[servlet名字]-servlet.xml”),Spring会处理这个配置文件。由此可见,Spring的配置文件也可放置在其他位置,只要在这里指定就可以了。如果定义了多个配置文件,则用“,”分隔即可。
    • servlet-mapping定义了所有以.do结尾的请求,都要经过分发器。

    当DispatcherServlet配置好后,一旦DispatcherServlet接受到请求,DispatcherServlet就开始处理请求了。

    DispatcherServlet处理流程

    当配置好DispatcherServlet后,DispatcherServlet接收到与其对应的请求之时,处理就开始了。处理流程如下:

    找到WebApplicationContext并将其绑定到请求的一个属性上,以便控制器和处理链上的其它处理器能使用WebApplicationContext。默认的属性名为DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE。

    将本地化解析器绑定到请求上,这样使得处理链上的处理器在处理请求(准备数据、显示视图等等)时能进行本地化处理。如果不需要本地化解析,忽略它就可以了。

    将主题解析器绑定到请求上,这样视图可以决定使用哪个主题。如果你不需要主题,可以忽略它。

    如果你指定了一个上传文件解析器,Spring会检查每个接收到的请求是否存在上传文件,如果是,这个请求将被封装成MultipartHttpServletRequest以便被处理链中的其它处理器使用。(Spring's multipart (fileupload) support查看更详细的信息)

    找到合适的处理器,执行和这个处理器相关的执行链(预处理器,后处理器,控制器),以便为视图准备模型数据。

    如果模型数据被返回,就使用配置在WebApplicationContext中的视图解析器显示视图,否则视图不会被显示。有多种原因可以导致返回的数据模型为空,比如预处理器或后处理器可能截取了请求,这可能是出于安全原因,也可能是请求已经被处理过,没有必要再处理一次。

    DispatcherServlet相关源码

    org.springframework.web.servlet.DispatcherServlet中doService方法部分源码:

    protected void doService(HttpServletRequest request,
                HttpServletResponse response) throws Exception {
        // ......
    
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,getWebApplicationContext());
        // ......
    }
    

    通过上面源码得知,DispatcherServlet会找到上下文WebApplicationContext(其指定的实现类为XmlWebApplicationContext),并将它绑定到一个属性上(默认属性名为WEB_APPLICATION_CONTEXT_ATTRIBUTE),以便控制器能够使用WebApplicationContext。

    initStrategies方法源码如下:

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

    从如上代码可以看出,DispatcherServlet启动时会进行我们需要的Web层Bean的配置,如HandlerMapping、HandlerAdapter等,而且如果我们没有配置,还会给我们提供默认的配置。

    DispatcherServlet SpringBoot自动配置

    DispatcherServlet在Spring Boot中的自动配置是通过DispatcherServletAutoConfiguration类来完成的。

    先看注解部分代码:

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass(DispatcherServlet.class)
    @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
    public class DispatcherServletAutoConfiguration {
        ...
    }
    

    @AutoConfigureOrder指定该自动配置的优先级;@Configuration指定该类为自动配置类;@ConditionalOnWebApplication指定自动配置需要满足是基于SERVLET的web应用;@ConditionalOnClass指定类路径下必须有DispatcherServlet类存在;@AutoConfigureAfter指定该自动配置必须基于ServletWebServerFactoryAutoConfiguration的自动配置。

    DispatcherServletAutoConfiguration中关于DispatcherServlet实例化的代码如下:

    @Configuration(proxyBeanMethods = false) // 实例化配置类
    @Conditional(DefaultDispatcherServletCondition.class) // 实例化条件:通过该类来判断
    @ConditionalOnClass(ServletRegistration.class) // 存在指定的ServletRegistration类
    // 加载HttpProperties和WebMvcProperties
    @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
    protected static class DispatcherServletConfiguration {
    
    	@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    	public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
    		// 创建DispatcherServlet
    		DispatcherServlet dispatcherServlet = new DispatcherServlet();
    		// 初始化DispatcherServlet各项配置
    		dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
    		dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
    		dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
    		dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
    		dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
    		return dispatcherServlet;
    	}
    
    	// 初始化上传文件的解析器
    	@Bean
    	@ConditionalOnBean(MultipartResolver.class)
    	@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    	public MultipartResolver multipartResolver(MultipartResolver resolver) {
    		// Detect if the user has created a MultipartResolver but named it incorrectly
    		return resolver;
    	}
    
    }
    

    内部类DispatcherServletConfiguration同样需要满足指定的条件才会进行初始化,具体看代码中的注释。

    其中的dispatcherServlet方法中实现了DispatcherServlet的实例化,并设置了基础参数。这对照传统的配置就是web.xml中DispatcherServlet的配置。

    另外一个方法multipartResolver,用于初始化上传文件的解析器,主要作用是当用户定义的MultipartResolver名字不为“multipartResolver”时,通过该方法将其修改为“multipartResolver”,相当于重命名。

    其中DispatcherServletConfiguration的注解@Conditional限定必须满足DefaultDispatcherServletCondition定义的匹配条件才会自动配置。而DefaultDispatcherServletCondition类同样为内部类。

    @Order(Ordered.LOWEST_PRECEDENCE - 10)
    private static class DefaultDispatcherServletCondition extends SpringBootCondition {
    
    	@Override
    	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    		ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
    		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    		List<String> dispatchServletBeans = Arrays
    				.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
    		if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
    			return ConditionOutcome
    					.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
    		}
    		if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
    			return ConditionOutcome.noMatch(
    					message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
    		}
    		if (dispatchServletBeans.isEmpty()) {
    			return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
    		}
    		return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
    				.items(Style.QUOTE, dispatchServletBeans)
    				.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
    	}
    
    }
    

    该类的核心功能,总结起来就是:检验Spring容器中是否已经存在一个名字为“dispatcherServlet”的DispatcherServlet,如果不存在,则满足条件。

    在该自动配置类中还有用于实例化ServletRegistrationBean的内部类:

    @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {
    
    	@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    	@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    	public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
    			WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
    		// 通过ServletRegistrationBean将dispatcherServlet注册为servlet,这样servlet才会生效。
    		DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
    				webMvcProperties.getServlet().getPath());
    		// 设置名称为dispatcherServlet
    		registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
    		// 设置加载优先级,设置值默认为-1,存在于WebMvcProperties类中
    		registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
    		multipartConfig.ifAvailable(registration::setMultipartConfig);
    		return registration;
    	}
    
    }
    

    DispatcherServletRegistrationConfiguration类的核心功能就是注册dispatcherServlet使其生效并设置一些初始化的参数。

    其中,DispatcherServletRegistrationBean继承自ServletRegistrationBean,主要为DispatcherServlet提供服务。DispatcherServletRegistrationBean和DispatcherServlet都提供了注册Servlet并公开DispatcherServletPath信息的功能。

    Spring Boot通过上面的自动配置类就完成了之前我们在web.xml中的配置操作。这也是它的方便之处。

    参考文章:

    https://www.cnblogs.com/wql025/p/4805634.html

    https://juejin.im/post/5d3066736fb9a07ece6806e4

    原文链接:《SpringBoot之DispatcherServlet详解及源码解析

    Spring技术视频

    CSDN学院:《Spring Boot 视频教程全家桶》


    程序新视界:精彩和成长都不容错过
    ![程序新视界-微信公众号](https://img2018.cnblogs.com/blog/1742867/201910/1742867-20191013111755842-2090947098.png)
  • 相关阅读:
    PAT (Advanced Level) Practice 1071 Speech Patterns (25分)
    PAT (Advanced Level) Practice 1070 Mooncake (25分)
    PAT (Advanced Level) Practice 1069 The Black Hole of Numbers (20分)
    PAT (Advanced Level) Practice 1074 Reversing Linked List (25分)
    PAT (Advanced Level) Practice 1073 Scientific Notation (20分)
    第一次冲刺个人总结01
    构建之法阅读笔记01
    人月神话阅读笔记01
    四则运算2
    学习进度条(软件工程概论1-8周)
  • 原文地址:https://www.cnblogs.com/secbro/p/11963568.html
Copyright © 2011-2022 走看看