• 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)
  • 相关阅读:
    [转].net批量导入数据
    IIS6 应用程序池配置详解附建议设置
    [转]SQLServer死锁问题
    VSS 版本管理
    非功能性需求介绍[转]
    javascript获取网页中HTML元素
    [转]如何做一流的研究
    多web并发测试问题解决
    jQuery 页面校验
    SqlServer排序规则错误致使不能查询
  • 原文地址:https://www.cnblogs.com/secbro/p/11963568.html
Copyright © 2020-2023  润新知