从web.xml谈谈SpringMVC集成spring的初始化流程及SpringBoot集成SpringMVC

一、关于servlet

  1. 详解servlet,https://www.runoob.com/servlet/servlet-tutorial.html
  2. 总览一下:
  • servlet与servlet容器

Java Servlet(Java服务器小程序)是一个基于Java技术的Web组件,运行在服务器端,它由Servlet容器所管理,用于生成动态的内容。 Servlet是平台独立的Java类,编写一个Servlet,实际上就是按照Servlet规范编写一个Java类。Servlet被编译为平台独立 的字节码,可以被动态地加载到支持Java技术的Web服务器中运行。

  • Servlet容器也叫做Servlet引擎,是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务,解码基于 MIME的请求,格式化基于MIME的响应。Servlet没有main方法,不能独立运行,它必须被部署到Servlet容器中,由容器来实例化和调用 Servlet的方法(如doGet()和doPost()),Servlet容器在Servlet的生命周期内包容和管理Servlet。在JSP技术 推出后,管理和运行Servlet/JSP的容器也称为Web容器。

(注:常用的MIME类型:text/html,application/pdf,video/quicktime,application /java,image/jpeg,application/jar,application/octet-stream,application/x- zip)

有了servlet之后,用户通过单击某个链接或者直接在浏览器的地址栏中输入URL来访问Servlet,Web服务器接收到该请求后,并不是将 请求直接交给Servlet,而是交给Servlet容器。Servlet容器实例化Servlet,调用Servlet的一个特定方法对请求进行处理, 并产生一个响应。这个响应由Servlet容器返回给Web服务器,Web服务器包装这个响应,以HTTP响应的形式发送给Web浏览器。

  • servlet容器能提供什么?

我们知道需要由servlet容器来管理和运行servlet,但是为什么要这样做呢?使用servlet容器的原因有:

  • 通信支持:利用容器提供的方法,你能轻松的让servlet与web服务器对话,而不用自己建立serversocket、监听某个端口、创建流等等。容器知道自己与web服务器之间的协议,所以你的servlet不用担心web服务器(如Apache,jetty)和你自己的web代码之间的API,只需要考 虑如何在servlet中实现业务逻辑(如处理一个订单)。

  • 生命周期管理:servlet容器控制着servlet的生与死,它负责加载类、实例化和初始化servlet,调用servlet方法,以及使servlet实例被垃圾回收,有了servlet容器,你不需要太多的考虑资源管理。

  • 多线程支持:容器会自动为它所接收的每个servlet请求创建一个新的java线程。针对用户的请求,如果servlet已经运行完相应的http服务方法,这个线程就会结束。这并不是说你不需要考虑线程安全性,其实你还会遇到同步问题,不过这样能使你少做很多工作。

  • 声明方式实现安全:利用servlet容器,你可以使用xml部署描述文件来配置和修改安全性,而不必将其硬编码写到servlet类代码中

  • JSP支持:servlet容器负责将jsp代码翻译为真正的java代码。

  • Servlet具有以下优点:
  • Servlet是单实例多线程的运行方式,每个请求在一个独立的线程中运行,而提供服务的Servlet实例只有一个。
  • Servlet具有可升级性,能响应更多的请求,因为Servlet容器使用一个线程而不是操作系统进程,而线程仅占用有限的系统资源。
  • Servlet使用标准的API,被更多的Web服务器所支持。
  • Servlet使用Java语言编写,因此拥有Java程序语言的所有优点,包括容易开发和平台独立性。
  • Servlet可以访问Java平台丰富的类库,使得各种应用的开发更为容易。
  • Servlet容器给Servlet提供额外的功能,如错误处理和安全。

其实,servlet就是一种使用http协议在服务器与客户端之间通信的技术。是Socket的一种应用

  • Tomcat

学习Servlet技术,就需要有一个Servlet运行环境,也就是需要有一个Servlet容器,本文用的是Tomcat。还有其他如jetty。Tomcat和IIS、Apache等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的 Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache,我们可以将Apache和Tomcat集成在 一起使用,Apache作为HTTP Web服务器,Tomcat作为Web容器。关于apache和tomcat的区别。

Tomcat服务器接受客户请求并做出响应的过程如下:

  1. 客户端(通常都是浏览器)访问Web服务器,发送HTTP请求。
  2. Web服务器接收到请求后,传递给Servlet容器。
  3. Servlet容器加载Servlet,产生Servlet实例后,向其传递表示请求和响应的对象。
  4. Servlet实例使用请求对象得到客户端的请求信息,然后进行相应的处理。
  5. Servlet实例将处理结果通过响应对象发送回客户端,容器负责确保响应正确送出,同时将控制返回给Web服务器。

二、从web.xml说起

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">


    <!-- spring配置文件,包括开启bean扫描,aop,事务 -->
    <!-- Spring加载的xml文件,不配置默认为applicationContext.xml -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-config.xml</param-value>
    </context-param>
    
    <!--ContextLoaderListener用于在启动web容器的时候,去上面的位置 读取配置文件并初始化Spring容器。启动父容器,即IOC容器,管理Dao,Service-->
    <!-- 该类作为spring的listener使用,它会在创建时自动查找web.xml配置的applicationContext.xml文件 -->
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <!--spring mvc配置-->
    <!-- 配置Spring MVC的DispatcherServlet,也可以配置为继承了DispatcherServlet的自定义类,这里配置spring mvc的配置(扫描controller) -->
    <!--用于启动子容器,也即是springMVC容器-->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
  • spring父容器的加载

Tomcat启动的时候会依次加载web.xml中配置的Listener、Filter和Servlet。所以根据上面的配置,会首先加载ContextLoaderListener,这个类继承了ContextLoader,用来初始化Spring根上下文,并将其放入ServletContext中。
在这里插入图片描述
实现 javax.servlet.ServletContextListener 接口,继承 ContextLoader 类,实现 Servlet 容器启动和关闭时,分别初始化和销毁 WebApplicationContext 容器

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	public ContextLoaderListener() {
	}
    /**
     * As of Spring 3.1, supports injecting the root web application context
     */
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}
	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
        // <1> 初始化 Root WebApplicationContext
		initWebApplicationContext(event.getServletContext());
	}
	/**
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
        // <2> 销毁 Root WebApplicationContext
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

下面就以这个为入口分析下代码。Tomcat容器首先会调用ContextLoadListener的contextInitialized()方法,这个方法又调用了父类ContextLoader的initWebApplicationContext()方法。下面是这个方法的源代码。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//如果ServletContext中已经存在Spring容器则报错
if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException(
				"Cannot initialize context because there is already a root application context present - " +
				"check whether you have multiple ContextLoader* definitions in your web.xml!");
	}
	Log logger = LogFactory.getLog(ContextLoader.class);
	servletContext.log("Initializing Spring root WebApplicationContext");
	if (logger.isInfoEnabled()) {
		logger.info("Root WebApplicationContext: initialization started");
	}
	long startTime = System.currentTimeMillis();
	try {
		// Store context in local instance variable, to guarantee that
		// it is available on ServletContext shutdown.
		if (this.context == null) {
            //这里创建了webApplicationContext,默认创建的是XmlWebApplicationContext
            //如果想要自定义实现类,可以在web.xml的<context-param>中配置contextClass这个参数
            //此时的Context还没进行配置,相当于只是个"空壳"
			this.context = createWebApplicationContext(servletContext);
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			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 ->
					// determine parent for root web application context, if any.
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
                //读取Spring的配置文件,初始化父上下文环境
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
        //将根上下文存入ServletContext中。
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if (ccl == ContextLoader.class.getClassLoader()) {
			currentContext = this.context;
		}
		else if (ccl != null) {
			currentContextPerThread.put(ccl, this.context);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
		}
		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
		}
		return this.context;
	}
	catch (RuntimeException ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
	catch (Error err) {
		logger.error("Context initialization failed", err);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
		throw err;
	}
}

spring体系下,ApplicationContext的集成体系
在这里插入图片描述

执行时序图:
在这里插入图片描述

至此,Spring的父(根)上下文已经初始化完毕,并且已经存在ServletContext中。

  • springMVC子容器的加载

DispatcherServlet的继承图如下
在这里插入图片描述
我们知道,加载web.xml中配置的Listener、Filter后,就会加载Servlet。在我们的web.xml中就是org.springframework.web.servlet.DispatcherServlet,而通过继承树我们知道,DispatcherServlet也是Servlet的实现,所以加载流程也是Servlet的流程。因为DispatcherServlet继承了FrameworkServlet,FrameworkServlet又继承了HttpServletBean,HttpServletBean又继承HttpServlet并且重写了init方法,所以创建子上下文时的入口就在这个init方法。

  • HttpServletBean.init(): 重写 GenericServlet 中的方法,负责将 ServletConfig 设置到当前 Servlet 对象中
public final void init() throws ServletException {
		 //读取Servlet配置的init-param,创建DispatcherServlet实例
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}
		// HttpServletBean的这个方法中没有做任何事情,子类FrameWorkServlet这个类的initServletBean()方法重写了
       // 这个类,所以后续工作会在这个方法中执行。
		initServletBean();
	}

下面是FrameWorkServlet这个类的initServletBean()方法

//FrameWorkServlet.initServletBean()
protected final void initServletBean() throws ServletException {
	getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
	if (this.logger.isInfoEnabled()) {
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
	}
	long startTime = System.currentTimeMillis();
	try {
        //这里是重点,初始化子Spring上下文
		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();
	}
	catch (ServletException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}
	catch (RuntimeException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}
	if (this.logger.isInfoEnabled()) {
		long elapsedTime = System.currentTimeMillis() - startTime;
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
				elapsedTime + " ms");
	}
}

下面是initWebApplicationContext()方法的具体代码

protected WebApplicationContext initWebApplicationContext() {
    // <1> 获得根 WebApplicationContext 对象
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    // <2> 获得 WebApplicationContext wac 对象
    WebApplicationContext wac = null;

    // 第一种情况,如果构造方法已经传入 webApplicationContext 属性,则直接使用
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        // 如果是 ConfigurableWebApplicationContext 类型,并且未激活,则进行初始化
        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);
                }
                // 配置和初始化 wac
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    // 第二种情况,从 ServletContext 获取对应的 WebApplicationContext 对象
    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();
    }
    // 第三种,创建一个 WebApplicationContext 对象
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }

    // <3> 如果未触发刷新事件,则主动触发刷新事件
    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.
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }

    // <4> 将 context 设置到 ServletContext 中
    if (this.publishContext) {
        // 将Spring子上下文存入ServletContext
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

最后看下DispatcherServlet中的onRefresh()方法,这个方法初始化了很多策略:
注意:FrameWorkServlet.onRefresh()只有DispatcherServlet中进行重写

//初始化九大组件
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

上面都是通过类似的机制,加载所有实现了该接口的类:
在这里插入图片描述

到此为止,SpringMVC的启动过程结束了。这边做下SpringMVC初始化总结:

  1. HttpServletBean的主要做一些初始化工作,将我们在web.xml中配置的参数设置到Servlet中;
  2. FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其和ServletContext关联;
  3. DispatcherServlet:初始化各个功能的实现类。比如异常处理、视图处理、请求映射处理等。

时序图:
在这里插入图片描述

三、传统的Spring MVC项目启动流程

  1. 如果在web.xml中配置了org.springframework.web.context.ContextLoaderListener,那么Tomcat在启动的时候会先加载父容器,并将其放到ServletContext中
  2. 然后会加载DispatcherServlet(这块流程建议从init方法一步步往下看,流程还是很清晰的),因为DispatcherServlet实质是一个Servlet,所以会先执行它的init方法。这个init方法在HttpServletBean这个类中实现,其主要工作是做一些初始化工作,将我们在web.xml中配置的参数书设置到Servlet中,然后再触发FrameworkServlet的initServletBean()方法;
  3. FrameworkServlet主要作用是初始化Spring子上下文,设置其父上下文,并将其放入ServletContext中;
  4. FrameworkServlet在调用initServletBean()的过程中同时会触发DispatcherServlet的onRefresh()方法,这个方法会初始化Spring MVC的各个功能组件。比如异常处理器、视图处理器、请求映射处理等。

在这里插入图片描述

四、SpringBoot集成SpringMVC

我们知道,SpringBoot的自动导入机制就是依赖spring framework内部使用的通用的工厂加载机制。其导入链如下:

@SpringBootApplication->
@EnableAutoConfiguration>
@Import({AutoConfigurationImportSelector.class})
AutoConfigurationImportSelector类下的

List<String>configurations=this.getCandidateConfigurations(annotationMetadata,attributes);
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

springFactoriesLoader是spring framework内部使用的通用的工厂加载机制,其可加载并实例化可能出现在classpath上的多个jar包中的META-INF/spring.factories文件中定义的指定类型的工厂,可视为一种类似于SPI的接口。 SpringBoot利用这种SPI接口实现了autoconfiguration机制:委托SpringFactoriesLoader来加载所有配置在META-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的值,spring-boot-autoconfiguration jar包中的META-INF/spring.factories中的EnableAutoConfiguration配置了众多供springboot导入的类。我们先回想一下SpringBoot预先读取的可配置Config类有哪个是与WebMVC相关的?经过比对我们发现在Key=EnableAutoConfiguration,Value=WebMvcAutoConfiguration就是关于SpringBoot自动配置WebMVC的可配置类,关于SpringBoot自动配置WebMVC的玄机也正是在这个配置类里面。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\

于是,我们必须好好看一下这个可配置类到底做了什么事情,能让SpringBoot启动完成之后也自动初始化好了一个WebMVC的环境。
在这里插入图片描述
点开可以看到这个可配置类上有很多注解修饰,这些注解分别限定在某些条件满足或不满足情况下,spring才会初始化并配置这个Config类。关于这些注解和文字解释如下:

//声明为配置类,并且Bean的方法不进行代理
@Configuration(proxyBeanMethods = false)
 
//判断当前环境是一个SERVLET环境,也就是说是Web环境下这个配置类才生效
@ConditionalOnWebApplication(type = Type.SERVLET)
 
//判断当前环境是否含有Servlet,DispatcherServlet,WebMvcConfigurer实例(前面SpringMVC章节零XML方式介绍的接口,
//类似引入web.xml的Java实例),这些都是WebMVC必不可少的组件,只有这些都存在这个配置类才生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
 
//判断当前环境是否存在WebMvcConfigurationSupport,如果不存在这个配置类才生效
//为什么这里不存在WebMvcConfigurationSupport时才能让配置类生效呢?因为这个接口是一个自定义配置WebMVC的接口,
//如果实现了这个接口就意味着开发者自己手动进行了webMVC的配置,那么SpringBoot就不再帮你自动配置了,防止了配置冲突。
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
 
//自动配置顺序:数值越低越优先配置
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
 
//WebMVC配置的核心注解,主要是对DispatcherServlet进行Java配置,以及对任务执行和校验器进行Java配置。
//当这个配置类生效之后,就会接着进行DispatcherServletAutoConfiguration,TaskExecutionAutoConfiguration,
//ValidationAutoConfiguration的配置,重点看DispatcherServletAutoConfiguration
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
      ValidationAutoConfiguration.class })
      
public class WebMvcAutoConfiguration {
    .....
}

从上面可以看到,SpringBoot要想自动配置WebMVC环境,那么是需要满足上述注解定义的一些条件:

  • 处于Web环境下
  • 容器中已经初始化好了WebMVC必须的组件
  • 用户没有自己手工配置过WebMVC

这些条件都满足后,SpringBoot就会为我们自动配置一个WebMVC环境,但是这个环境是怎么样的呢(比如采用什么容器,端口号是什么,DispatchServlet怎么配置,Resover怎么配置,Converter怎么配置,等等)?这个就是最后一个注解@AutoConfigureAfter内声明的来定义了,实际上也就是由DispatcherServletAutoConfiguration.class通过Java零XML配置方式在代码里为我们提前写好定义的环境。

对于核心DispatcherServlet的自动配置,点开这个核心DispatcherServletAutoConfiguration.class

在这里插入图片描述

可以看到这个DispatcherServlet自动配置类也有很多注解修饰:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
//如果环境中已经有了用户自己配置的DispatcherServlet,就不再自动配置
@ConditionalOnClass(DispatcherServlet.class)
//在这个DispatcherServlet自动配置之后,通过ServletWebServerFactoryAutoConfiguration对web容器进行配置
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)

在这个DispatcherServlet自动配置类中,最核心的就是对DispatcherServlet的配置,可以看到对DispatcherServlet配置的核心代码在静态内部类DispatcherServletConfiguration的dispatcherServlet方法上,通过@Bean返回给Spring容器:

在这里插入图片描述

从这个方法内部我们就看出了,SpringBoot自动配置DispatcherServlet时是直接new的,然后设置各种参数最终通过@Bean交给Spring容器。在上面的方法执行结束将一个DispatcherServlet交给Spring容器后,接下来会调用静态内部类DispatcherServletRegistrationConfiguration的dispatcherServletRegistration方法,对DispatcherServlet的参数进行配置

在这里插入图片描述

这里设置DispatcherServlet名字为dispatcherServlet;启动顺序是默认值-1(使用时加载);请求接收路径是/;配置完成后交给Spring容器,这就是对DispatcherServlet的Java代码配置了

不过这里有一个问题,就是这里返回给Spring容器的是一个DispatcherServletRegistrationBean,那么Tomcat等Web容器时怎么将这个DispatcherServletRegistrationBean内包含的DispatcherServlet信息解析出来并生效运行呢?这个知识点有必要做一下说明。

首先看这个DispatcherServletRegistrationBean的构造是怎么样的:

在这里插入图片描述

这个DispatcherServletRegistrationBean继承了ServletRegistrationBean,这个ServletRegistrationBean是携带了供Tomcat容器解析加载的DispatcherServlet,让我们看下这个ServletRegistrationBean的继承关系图:

在这里插入图片描述

就可以知道这个DispatcherServletRegistrationBean实际最终是实现了ServletContextInitializer接口,看到这里如果前面对Servlet的SPI有所了解很快就能反应过来,这里是有一个onStartup方法通过SPI机制让Tomcat启动时能自动调用到onStartup里面来,通过下面调用链最终Spring容器进行了addServlet操作将DispatchServlet添加到Spring容器中并生效,最后进行config方法对DispatcherServlet进行设置:

关于SPI机制可以参考这篇文章
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

但是读了上面的源码又引发了两个很值得深思的问题

  1. 在静态内部类DispatcherServletConfiguration的dispatcherServlet方法上,通过new一个DispatcherServlet之后直接用@Bean返回给Spring容器了。在学习SpringMVC时我们知道DispatcherServlet需要传入一个ApplicationContext上下文,如果没有传递则会去默认配置文件解析出一个上下文。但是这里这个DispatcherServlet却没有关联任何Spring上下文的地方却能使DispatcherServlet起作用,那这个关联操作到底是在哪里进行的呢?

这需要我们看SpringBoot中DispatcherServlet的源码是怎么写的,让我们看下有参的构造是怎么样的,因为无参构造只是一个空方法:

在这里插入图片描述
在这里插入图片描述
使用IDEAJ的查找工具,看下这个this.webApplicationContext在哪些地方被设置,可以找到是在setApplicationContext方法内进行设置的:
在这里插入图片描述

看到这个重写方法以及方法注释,根据Spring的知识我们应该猜想到这个类应该是实现了ApplicationContextAware接口。ApplicationContextAware接口是做什么的呢?这个接口是当Spring容器初始化结束之后,实现了ApplicationContextAware接口的实现类就会被调用并且执行setApplicationContext方法。回到这里也就是说当Spring容器初始化完成之后,由于实现了ApplicationContextAware接口,于是会执行setApplicationContext方法,在这个方法中将初始化完成的Spring上下文赋值给this.webApplicationContext变量(这个变量就是DispatchServlet内部的Spring上下文变量),于是就解释了为什么SpringBoot在new一个DispatchServlet时不需要传入Spring上下文的原因

  1. 理解了上面的问题和答案,就引出第二个问题:

前面学习SpringMVC时知道对DispatcherServlet的配置是将Spring容器对象作为参数传给DispatcherServlet;但是SpringBoot自动配置WebMVC时却是将DispatcherServlet对象传给Spring容器;这两种场景的实现是反过来的,为什么会这样设计呢?其实这两种方式的区别就在于它们分别是怎么接在Spring容器的。

回答为什么SpringBoot是将DispatchServlet传给Spring容器的问题,就要结合上面第一个问题的解析。这是因为要调用实现了ApplicationContextAware接口的实现类,则必须保证这个实现类在Spring容器当中才能生效。也就是说SpringBoot中实现了ApplicationContextAware接口的DispatchServlet要想触发接口方法,则必须作为一个Bean存在于Spring容器中,这就是SpringBoot为什么要将DispatchServlet作为Bean传入到Spring容器中的原因了

  1. 上面介绍完SpringBoot对DispatchServlet进行自动配置的细节,那么还为我们做了哪些组件配置呢?这里要回过来看WebMvcAutoConfiguration

在这个自动配置类中,定义了一个静态内部类WebMvcAutoConfigurationAdapter,实现了WebMvcConfigurer接口。这个就和之前学习SpringMVC应用时介绍的一样,通过实现WebMvcConfigurer接口就可以拥有一个类似web.xml功能,可以对其它组件进行配置操作:

public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer

在这个里面实现了默认的Resolver,MessageConverters,ViewResolver等组件。如果我们要添加自定义的组件怎么办呢?对于SpringMVC而言我们必须在方法中将这个自定义的组件添加到对应组件集合中,但是SpringBoot对此进行的拓展,使得我们可以直接通过@Bean的方式就可以添加自定义组件(这一点在SpringMVC是做不到自定义添加的):

在这里插入图片描述

五、总结一下SpringBoot除了自动配置DispatchServlet之外,还配置了以下

  1. spring boot会默认注入一个视图解析器:ContentNegotiatingViewResolver
    主要做2个事情:
  • 整合所有的视图解析器
  • 遍历所有的视图解析器选一个最佳的方案

spring boot 会在当前的spring容器里面找到所有HttpMessageConverter类型的Bean 封装成一个集合

  • spring boot会自己注入一个默认的实现 jackson
  • 如果用户需要自己配置的话 只需要@Bean
  1. SpringBoot实现一个字符串到日期的参数转换器:

每当前端要将字符串日期传递到后台之后,要自动转换成日期格式类型时,可以写一个Converter进行转换。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

至此,关于SpringBoot自动配置WebMVC的源码原理就分享到这里了。

  • 以springmvc自动装配为例看底层源码细节:配置类WebMvcAutoConfiguration.
  • 这个配置类有多个注解分别限定在某些条件满足或不满足情况下,spring才会初始化并配置这个Config类,
  • 其中注解@AutoConfigureAfter中DispatcherServletAutoConfiguration是自动装配的核心类,
  • 这里主要对DispatcherServlet进行配置,和servlet被tomcat调用onStartup()一样
  • 需要先new出DispatcherServlet并放入Spring容器,然后在tomcat调用前进行addXXX参数的操作,
  • 在springboot实现的javaConfig里,filter,listener,DispatcherServlet,servlet这些servler子类都用XXXRegistrationBean来封装,
  • 这些XXXRegistrationBean都实现了RegistrationBean超类(这个超类拥有onStartup方法),于是保证这些Bean都可以被 tomcat识别并执行,在执行各种不同类型的Bean时,会调用不同的register或configure方法进行add各自不同参数的方法,
  • 从而实现了各种servlet组件参数的自动配置和加载步骤。对于web容器的配置是ServletWebServerFactoryAutoConfiguration配置类中进行主要对web容器类型IP端口连接数等参数进行配置。

参考文章

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值