【Spring】抽丝剥茧SpringMVC-DispatcherServlet

本文源码基于Spring版本4.3.18, SpringBoot版本1.5.14.RELEASE

DispatcherServlet

    使用SpringMVC框架的同学一定知道DispatcherServlet。SpringMVC处理Http请求就是从DispatcherServlet开始。SpringMC简化了Servlet规范的开发模式,定义了通用了Servlet(DispatcherServlet)与Servlet容器(jetty/tomcat)交互。开发者专注于业务逻辑的开发。下图是DispatcherServlet的类图:

【Spring】抽丝剥茧SpringMVC-DispatcherServlet_第1张图片

 

可以看出DispatcherServlet其实就是一个Servlet,它核心功能就是分发Http请求到具体的处理器。

DispatcherServlet分发请求

源码基于Spring版本4.3.18

【Spring】抽丝剥茧SpringMVC-DispatcherServlet_第2张图片

    上图描述了DispatcherServlet处理请求的核心逻辑

  • 更新线程变量RequestContextHolder:设置线程变量,应用在可以获取RequestAttributes对象,进而获取Request对象。Spring中注入Request对象就是利用这一点Spring如何注入ServletRequest;
  • 获取处理器getHandler:这里是一个配置点位,开发者需要配置HandlerMapping,HandlerMapping返回包含拦截器的Handler,典型的HandlerMapping就是RequestMappingHandlerMapping(配套使用@RequestMapping);
  • 匹配Handler适配器:同样这里也是一个配置点位,开发者需要配置HandlerAdapter,最典型的HandlerAdapter就是RequestMappingHandlerAdapter(配套使用RequestMappingHandlerMapping),使用SpringMvc的应用大部分都是这种适配器;
  • 304处理:这里缓存处理,根据请求中的etag、last_modified等请求头判断资源是否更新,没有则返回304告诉浏览器从本地的缓存拿数据;
  • 执行Hander预处理、执行Hander后处理、请求完成处理:这三个步骤都是依次执行各拦截器的preHandle、postHandle、afterCompletion,拦截器也是一个配置点位,开发者可以配置HandlerInterceptor;
  • 通过适配器执行Handler:这里就会执行请求对应的处理方法,后面会专门分析下RequestMappingHandlerAdapter的执行过程,这里略过;
  • 异常处理:前面任何步骤抛出异常将被捕获并记录下来,进入异常处理分支,异常处理也是一个配置点位,开发者需要配置HandlerExceptionResolver。只要某个异常处理器返回ModelAndView对象就会终止剩下异常处理器执行。
  • 视图解析器解析View对象:视图解析器是一个配置点位,开发者需要配置ViewResolver。Handler执行或者异常处理可能会返回ModelAndView对象。如果返回了ModelAndView对象且该对象里面包含的view是字符串(代表view的name),依次调用视图解析器解析view,解析出view即终止剩下的视图解析器;
  • 渲染视图:如果前面返回了ModelAndView对象且视图解析器也解析出了View对象,则会执行View.render来渲染,View也是配置点位,开发者可以配置不同的视图实现类,典型的就是FreeMarkerView(处理模板视图)和MappingJackson2JsonView(处理json对象)。

    SpringMVC遵守着MVC模型(即数据-展示-控制器),Hander处理业务逻辑返回ModleAndView对象(包含视图和数据Model)或者NULL,视图渲染交给视图解析器和各个视图实现类。DispatcherServlet的主体流程中有很多配置点位,需要开发者在开发中配置,当然SpringMVC也会进行默认配置。SpringMVC为每种配置点位提供了多种配置方式,所以搞清楚这些配置是一个庞大的工程。不过,要搞清楚SpringMVC就必须搞清楚这些配置点位是如何配置。后面就顺着这个脉络梳理每个配置点位的配置、使用及常见问题。

RequestMappingHandlerAdapter适配器

SpringMVC最常见配置的HandlerMapping就是RequestMappingHandlerMapping,其对应的适配器是RequestMappingHandlerAdapter。

【Spring】抽丝剥茧SpringMVC-DispatcherServlet_第3张图片

    

  • 创建DataBindFactory:先收集所有符合此Handler的@InitBinder方法,这些方法可以在Controller或者ControllerAdvice里用注解@InitBinder修饰的方法,这些方法用来初始化DataBinder。具体的工厂是ServletRequestDataBinderFactory对象。开发者可以@InitBinder方法达到定制DataBinder的效果。
  • 创建ModelFactory:先收集所有符合此Handler的@ModelAttribute方法,这些方法可以在Controller或者ControllerAdvice里用注解@ModelAttribute修饰且没有@RequestMapping修饰的方法,这些方法用来初始化Model对象。具体的工厂是ModelFactory对象。
  • 解析实际参数:这里从Request里解析出目标方法的实际参数,DataBinder在这里起到关键性作用,开发者可以自定义HandlerMethodArgumentResolver,SpringMVC也有默认的配置。
  • @ResponseStatus处理:如果目标方法或者其类上有@ResponseStatus注解,则这里会设置Response的status。如果@ResponseStatus注解中reason设置值或者目标方法返回的对象是空,那么本次http请求将被设置为已处理,最后就不会返回ModelAndView对象。
  • returnValue处理:对目标方法返回的return值进行处理。这里开发者可以自定义HandlerMethodReturnValueHandler,SpringMVC也有默认的配置。比较典型的RequestResponseBodyMethodProcessor就是处理@ResponseBody注解,并且设置本次请求为已处理,最后就不会返回ModelAndView对象。
  • 返回ModelAndView对象:如果本次http请求被设置成了已处理,则不会返回ModelAndView对象,那么DispatcherServlet也就会不执行视图渲染逻辑。@ResponseBody、@RestController等等场景下就不会返回ModelAndView对象。

    同样,RequestMappingHandlerAdapter也包含了很多扩展:InitBinder定制DataBinder、ModuleAttribute初始化Model、参数解析器解析参数、returnValue处理器处理返回值。

配置DispatcherServlet

web.xml

    这是最原始的配置方式,在应用的web.xml(路径一般是src/main/webapp/WEB-INF)中配置指定为DispatcherServlet,并且需要指定一个bean的xml。DispatcherServlet在初始化的时候会根据这个路径创建ApplicationContext。

    这种方式的启动原理是servlet容器(jetty/tomcat)在启动时解析这个web.xml,依次创建需要的对象,包括Filter、Listener、Servlet。


	
		dispatcherServlet
		org.springframework.web.servlet.DispatcherServlet
		
			contextConfigLocation
			WEB-INF/spring/servlet-context.xml
		
		
			dispatchOptionsRequest
			true
		
		1
	

    
        dispatcherServlet
        /
    

 

WebApplicationInitializer

    servlet 3.0以上的规范中支持动态添加servlet,基于此Spring从3.2版本开始支持编码添加servlet,而不需要配置web.xml。开发者需要自定义启动类继承AbstractAnnotationConfigDispatcherServletInitializer,其类图如下:

【Spring】抽丝剥茧SpringMVC-DispatcherServlet_第4张图片

定义类继承AbstractAnnotationConfigDispatcherServletInitializer,重写getServletConfigClasses,该方法返回类将作为DispatcherServlet中ApplicationContext的配置类

public class FocuseInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    protected Class[] getRootConfigClasses() {
        return null;
    }

    /**
     * 返回配置类 @Configuration或Component注解
     **/
    protected Class[] getServletConfigClasses() {
        Class[] result = new Class[1];
        result[0] = ApplicationConfig.class;
        return result;
    }

    protected String[] getServletMappings() {
        String[] result = new String[1];
        result[0] = "/*";
        return result;
    }
}

ApplicationConfig里面配置扫描路径或者@Bean等bean的定义

/**
 * @author :
 * @date :Created in 2020/11/30 上午11:18
 * @description:
 * @modified By:
 */
@Configuration
@ComponentScan(basePackages = {"com.focuse.mvcdemo"})
public class ApplicationConfig {
}

这样配置就完成了,此种方式配置Servlet的启动原理是servlet 3.0规范中引入的javax.servlet.ServletContainerInitializer机制(参考Servlet规范ServletContainerInitializer)

SpringBoot

进入SpringBoot时代后,SpringBoot为我们更简便的方式配置DispatcherServlet,隐藏了添加Servlet的逻辑。使用SpringBoot有两种方式添加DispatcherServlet。其一是直接用starter,DispatcherServletAutoConfiguration会注册DispatcherServlet的bean和ServletRegistrationBean的bean;其二是开发者自己注入ServletRegistrationBean的Bean。

starter-web

引入starter


        1.8
        4.3.17.RELEASE
        3.0-alpha-1
        1.5.14.RELEASE
    

    
        
            org.springframework.boot
            spring-boot-starter-web
            ${boot.version}
            
            
                
                    org.springframework.boot
                    spring-boot-starter-tomcat
                
            
        

        
            org.springframework.boot
            spring-boot-starter-jetty
            ${boot.version}
        

        
            javax.servlet
            servlet-api
            ${servlet.version}
        

    

这种方式的启动原理就是SpringBoot的AutoConfiguration机制,starter-web配置方式跟下面介绍的【ServletRegistrationBean】方式本质上是一样的,只不过starter-web方式帮开发者自动配置了对应的Bean,具体的自动配置源码在org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,此处不做赘述。

ServletRegistrationBean(ServletContextInitializer)

   此种方式的原理机制是SpringBoot启动时会调用所有ServletContextInitializer类型Bean的onStartup方法。ServletRegistrationBean是其实现类

【Spring】抽丝剥茧SpringMVC-DispatcherServlet_第5张图片

public class ServletRegistrationBean extends RegistrationBean {

... ...

   public void onStartup(ServletContext servletContext) throws ServletException {
		Assert.notNull(this.servlet, "Servlet must not be null");
		String name = getServletName();
		if (!isEnabled()) {
			logger.info("Servlet " + name + " was not registered (disabled)");
			return;
		}
		logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);
		Dynamic added = servletContext.addServlet(name, this.servlet);
		if (added == null) {
			logger.info("Servlet " + name + " was not registered "
					+ "(possibly already registered?)");
			return;
		}
		configure(added);
	}

... ...
}

可以看到ServletRegistrationBean其实就是往ServletContext中添加Servlet,其原理与【WebApplicationInitializer】类似,都要求Servlet容器支持3.0以上的规范。而starter-web就是隐藏了ServletRegistrationBean的注册,在DispatcherServletAutoConfiguration中自动注册。

用ServletRegistrationBean方式,开发者需要自己注册Bean并且禁用DispatcherServletAutoConfiguration,如下:

/**
 * @author :
 * @date :Created in 2020/11/30 下午1:36
 * @description:
 * @modified By:
 */
@SpringBootApplication(exclude = {DispatcherServletAutoConfiguration.class})
@Import(ApplicationConfig.class)
public class Launcher {
    public static void main(String[] args) {
        SpringApplication.run(Launcher.class);
    }

    @Bean
    public DispatcherServlet dispatchServlet() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        return dispatcherServlet;
    }

    @Bean
    public ServletRegistrationBean dispatchServletBean(DispatcherServlet dispatcherServlet) {
        ServletRegistrationBean dispatchServletBean = new ServletRegistrationBean();
        dispatchServletBean.setLoadOnStartup(1);
        dispatchServletBean.addUrlMappings("/*");
        dispatchServletBean.setServlet(dispatcherServlet);
        return dispatchServletBean;
    }
}

@WebServlet注解

Servlet 3.0规范中引入注解@WebServlet,Servlet容器启动的时候扫描所有被该注解的所注解的类,并添加到容器中。所以如果定义了子类继承DispatcherServlet并用@WebServlet注解修饰,也可添加该Servlet到Servlet容器中。 此处略!

DispatcherServlet初始化

    从DispatcherServlet类图看出,它实际上是一个Servlet。Servlet规范中,servlet容器启动的时候会调用每个servlet的init(ServletConfig config),具体的实现在GenericServlet中。Servlet接口定义如下:

public interface Servlet {

    /**
     * Called by the servlet container to indicate to a servlet that the 
     * servlet is being placed into service.
     * ... ...
     *
     */

    public void init(ServletConfig config) throws ServletException;
    
    public ServletConfig getServletConfig();
   
    /**
     * Called by the servlet container to allow the servlet to respond to 
     * a request.
     *
     * 

This method is only called after the servlet's init() * method has completed successfully. * *

The status code of the response always should be set for a servlet * that throws or sends an error. * */ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy(); }

GenericServlet中init实现如下:

public abstract class GenericServlet 
    implements Servlet, ServletConfig, java.io.Serializable{
   public void init(ServletConfig config) throws ServletException {
	   this.config = config;
	   this.init();
    }
}

GenericServlet的init调用HttpServletBean的init,进而调用FrameworkServlet的initServletBean,然后调用链到达FrameworkServlet的initWebApplicationContext。

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
... ...
    protected WebApplicationContext initWebApplicationContext() {
		... ...

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

		... ...

		return wac;
	}
... ...
}

这里先初始化ApplicationContext(这部分逻辑忽略,不在此处讨论),如果没有收到过"refreshEvent",则调用onRefresh;如果收到过则不会调用。那么我们看下收到事件时处理什么?

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
... ...
    public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		onRefresh(event.getApplicationContext());
	}
... ...
}

如果收到刷新事件,也是调用onRefresh方法。onRefresh调用DispatcherServlet的initStrategies。initStrategies从ApplicationContext取出各种各样的bean装配到DispatcherServlet的配置。执行完这些initXX方法,开发者配置的视图解析器、HandlerMapping、异常处理器等等就装配到DispatcherServlet上了,其在后续分发请求时就会使用到这些组件。

public class DispatcherServlet extends FrameworkServlet {
... ...
    protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
... ...
}

初始化的调用过程如下图

【Spring】抽丝剥茧SpringMVC-DispatcherServlet_第6张图片

总结

    本文从整理流程介绍DispatcherServlet的核心逻辑、配置方式、初始化装配过程。DispatcherServlet的整个知识体系如下图。

【Spring】抽丝剥茧SpringMVC-DispatcherServlet_第7张图片



目录 目录

下一篇 RequestMappingHandlerMapping   

再下一篇 RequestMappingHandlerAdapter

你可能感兴趣的:(SpringMVC,spring,spring,boot,Servlet,SpringMVC,web后端)