本文源码基于Spring版本4.3.18, SpringBoot版本1.5.14.RELEASE
使用SpringMVC框架的同学一定知道DispatcherServlet。SpringMVC处理Http请求就是从DispatcherServlet开始。SpringMC简化了Servlet规范的开发模式,定义了通用了Servlet(DispatcherServlet)与Servlet容器(jetty/tomcat)交互。开发者专注于业务逻辑的开发。下图是DispatcherServlet的类图:
可以看出DispatcherServlet其实就是一个Servlet,它核心功能就是分发Http请求到具体的处理器。
源码基于Spring版本4.3.18
上图描述了DispatcherServlet处理请求的核心逻辑
SpringMVC遵守着MVC模型(即数据-展示-控制器),Hander处理业务逻辑返回ModleAndView对象(包含视图和数据Model)或者NULL,视图渲染交给视图解析器和各个视图实现类。DispatcherServlet的主体流程中有很多配置点位,需要开发者在开发中配置,当然SpringMVC也会进行默认配置。SpringMVC为每种配置点位提供了多种配置方式,所以搞清楚这些配置是一个庞大的工程。不过,要搞清楚SpringMVC就必须搞清楚这些配置点位是如何配置。后面就顺着这个脉络梳理每个配置点位的配置、使用及常见问题。
SpringMVC最常见配置的HandlerMapping就是RequestMappingHandlerMapping,其对应的适配器是RequestMappingHandlerAdapter。
同样,RequestMappingHandlerAdapter也包含了很多扩展:InitBinder定制DataBinder、ModuleAttribute初始化Model、参数解析器解析参数、returnValue处理器处理返回值。
这是最原始的配置方式,在应用的web.xml(路径一般是src/main/webapp/WEB-INF)中配置
这种方式的启动原理是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
/
servlet 3.0以上的规范中支持动态添加servlet,基于此Spring从3.2版本开始支持编码添加servlet,而不需要配置web.xml。开发者需要自定义启动类继承AbstractAnnotationConfigDispatcherServletInitializer,其类图如下:
定义类继承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为我们更简便的方式配置DispatcherServlet,隐藏了添加Servlet的逻辑。使用SpringBoot有两种方式添加DispatcherServlet。其一是直接用starter,DispatcherServletAutoConfiguration会注册DispatcherServlet的bean和ServletRegistrationBean的bean;其二是开发者自己注入ServletRegistrationBean的Bean。
引入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,此处不做赘述。
此种方式的原理机制是SpringBoot启动时会调用所有ServletContextInitializer类型Bean的onStartup方法。ServletRegistrationBean是其实现类
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;
}
}
Servlet 3.0规范中引入注解@WebServlet,Servlet容器启动的时候扫描所有被该注解的所注解的类,并添加到容器中。所以如果定义了子类继承DispatcherServlet并用@WebServlet注解修饰,也可添加该Servlet到Servlet容器中。 此处略!
从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);
}
... ...
}
初始化的调用过程如下图
本文从整理流程介绍DispatcherServlet的核心逻辑、配置方式、初始化装配过程。DispatcherServlet的整个知识体系如下图。
目录 目录
下一篇 RequestMappingHandlerMapping
再下一篇 RequestMappingHandlerAdapter