细说Spring Boot初始化DispatcherServlet

目录

DispatcherServlet概述

Spring Boot自动初始化DispatcherServlet


DispatcherServlet概述

在Spring Boot框架未出现之前,要开发一个基于Spring MVC框架的项目,通常需要在Java web项目的描述符文件web.xml中添加如下配置:

<!-- 初始化Spring IoC容器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 手动配置DispatcherServlet  -->
<servlet>
    <servlet-name>SpringMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:/spring*.xml</param-value>
    </init-param>
</servlet>

<!-- 配置DispatcherServlet拦截路径,让所有Web请求都经过DispatcherServlet -->
<servlet-mapping>
    <servlet-name>SpringMVC</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

添加如上配置之后,就能实现将Spring MVC框架整合到Web项目中了,那这是怎么做到的呢?下面一一解答下。

首先,org.springframework.web.context.ContextLoaderListener实现了接口javax.servlet.ServletContextListener,这样就能确保Web应用在启动后回调该接口。

public interface ServletContextListener extends EventListener {
	// Web应用在启动之后会调用该方法,通过传递的事件参数获取到ServletContext上下文环境
    public void contextInitialized(ServletContextEvent sce);

	// Web应用在停止后会回调该方法
    public void contextDestroyed(ServletContextEvent sce);
}

其次,在org.springframework.web.context.ContextLoaderListener中实现了contextInitialized()方法,并且在方法实现中触发了对Spring IoC容器的初始化,并且将ServletContext上下文保存到了IoC容器中。

// ServletContextListener
@Override
public void contextInitialized(ServletContextEvent event) {
    // 触发对Spring IoC容器的初始化
    initWebApplicationContext(event.getServletContext());
}

// AbstractRefreshableWebApplicationContext
@Override
public void setServletContext(@Nullable ServletContext servletContext) {
    // 在IoC容器中保存了ServletContext上下文
    this.servletContext = servletContext;
}

既然Spring IoC容器已经和ServletContext建立了联系,那当Web请求被DispatcherServlet拦截之后就可以基于Spring IoC容器环境进行处理了,实际上这样就建立了一个从Serlvet到Spring MVC框架的桥梁。

至此,关于Spring MVC框架如何与Web应用集成的问题就算得到了解答,很显然在传统的Spring MVC项目中,这是通过手动配置实现的。而在使用Spring Boot框架时就没有再看到这些配置了, Spring Boot的强大之处在于自动装配机制,虽然我们没有手动去配置,实际上是Spring Boot框架帮我们自动实现了。 那么,Spring Boot是如何实现的呢?

Spring Boot自动初始化DispatcherServlet

如下解读基于Spring Boot 2.7.14版本进行。

经过对Spring Boot的源码解读和梳理后知道,在Spring Boot框架中DispatcherServlet的自动装配是通过注解@DispatcherServletAutoConfiguration实现的。 具体流程如下:

首先,在Spring Boot的核心注解@EnableAutoConfiguration中引入了一个类AutoConfigurationImportSelector,Spring Boot在启动时会触发该类中如下方法的调用。

// AutoConfigurationImportSelector.getCandidateConfigurations()
// 该方法是在Spring Boot启动时调用的,具体来说是在刷新Spring IoC容器的时候触发的
// 在该方法中实现加载自动配置类,具体来说,是加载2个配置文件中的自动配置类
// 其一,加载各种starter组件jar中"META-INF/spring.factories"文件指定的自动配置类
// 其二,加载Sring Boot自己的自动配置类,这些类在文件"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"中指定
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 首先加载各类starter组件jar包中"META-INF/spring.factories"文件指定的自动配置类
    List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    // 再加载文件"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"中指定的自动配置类
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    return configurations;
}

其次,在加载Spring Boot的“META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports”自动配置类的时候,就会加载到org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration

DispatcherServletAutoConfiguration的源码中可以很清晰地看到正是在该配置类中实现了对dispatcherServlet的注入。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
public class DispatcherServletAutoConfiguration {
    @Configuration(proxyBeanMethods = false)
	@Conditional(DefaultDispatcherServletCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	protected static class DispatcherServletConfiguration {
        
        // 实际上是在DispatcherServletAutoConfiguration的内部静态类DispatcherServletConfiguration中完成了对"dispatcherServlet"的注入
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
			dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
			return dispatcherServlet;
		}

		// 省略了其他代码
        // ...
	}
}

虽然解答了DispatcherServlet的自动注入问题,但是还没有解答Spring IoC容器是如何与ServletContext上下文建立联系的。 经过对Spring Boot启动流程的源码解读知道,其实建立Spring IoC容器与ServletContext的关系是在ServletWebServerApplicationContext.onRefresh()方法中实现的。

// 在刷新Spring IoC容器的过程中会调用该方法
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

private void createWebServer() {
    // 在这里会完成对Servlet容器的创建,并且与ServletContext上下文进行关联
}
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
private void selfInitialize(ServletContext servletContext) throws ServletException {
	// <202106061603>
	prepareWebApplicationContext(servletContext);
	// 忽略
	registerApplicationScope(servletContext);
	// 忽略
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	// 逐个调用ServletContextInitializer,比如DispatcherServlet
	// 的注册就是通过这里的方法调用完成的,这个我们在"3:springboot如何使用servlet"
	// 已经分析过了
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}

<202106061603>处是注册ROOT IOC容器到servletcontext中,该处执行的逻辑和常规springMVC程序中ContextLoaderListener执行的操作类似,都是IOC容器和servletcontext的关联,源码如下(3:springboot如何使用servlet,2.4.4的源码在):

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#prepareWebApplicationContext
protected void prepareWebApplicationContext(ServletContext servletContext) {
	// 获取ROOT IOC 容器
	Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	// 如果是IOC容器不为空
	if (rootContext != null) {
		// 如果是已存在的和当前的相等
		if (rootContext == this) {
			// 是否多个ServletContextInititalizer导致重复的异常信息提示
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - "
							+ "check whether you have multiple ServletContextInitializers!");
		}
		// 已存在,且不是当前的,则直接return
		return;
	}
	Log logger = LogFactory.getLog(ContextLoader.class);
	// 该日志在springboot程序启动时会看到,打印初始化内嵌web应用的IOC容器开始信息
	// ,是springboot启动过程中的重要节点
	servletContext.log("Initializing Spring embedded WebApplicationContext");
	try {
		// 设置IOC容器到servletcontext中
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
		if (logger.isDebugEnabled()) {
			logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
					+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
		}
		// 调用servletcontext属性的写方法设置到全局变量
		setServletContext(servletContext);
		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - getStartupDate();
			logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
		}
	}
	catch (RuntimeException | Error ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}

所以,Spring Boot通过自动装配机制完成了对Spring MVC的DispatcherServlet注入,并且还创建了嵌入式的Servlet容器,并以Deamon线程方式运行在后台。

疑问在进入getSelfInitializer方法前,代码跳入了DispatcherServletConfiguration#dispatcherServlet方法,没看懂这是哪里触发的(版本2.4.4):

相关文章:

springboot之IOC容器ServletWebServerApplicationContext分析

SpringBoot之DispatcherServlet详解及源码解析

spring boot DispatcherServlet(一)初始化和注册

猜你喜欢

转载自blog.csdn.net/meser88/article/details/138126157