SpringMVC源码--DispatcherServlet 初始化

本文深入剖析了SpringMVC核心组件DispatcherServlet的初始化过程,包括成员变量如handlerMappings、multipartResolver的初始化。DispatcherServlet在Servlet容器中启动时,会调用init方法进行配置。在找不到自定义的HandlerMapping时,它会使用默认策略从DispatcherServlet.properties文件中加载并由IOC容器创建Bean。此外,文章还介绍了multipartResolver的初始化,通过检查ApplicationContext中是否存在指定类型的bean来完成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、SpringMVC 传统配置方式
1. 配置 web.xml

Servlet容器(Tomcat、Jetty等) 在启动时,会解析应用 classpath路径下的文件: META-INF/web.xml

web.xml文件, 可以添加自定义的Servlet、Filter、Listener。

在SpringMVC中使用DispatcherServlet ,并且路径通常设置为 / , 表示匹配所有的请求

<web-app>
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
2. spring-mvc 配置文件
  <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/spring-mvc.xml</param-value>
  </init-param>

在创建DispatcherServlet 时, 设置contextConfigLocation参数,用于指定 SpringMVC配置文件的路径。 该配置文件用于定义与 SpringMVC 相关的配置(核心组件、controller等)

可能的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd">
 
    <context:component-scan base-package="com.mvc.controller"/>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
        <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
    </bean>

</beans>
3. spring 配置

该文件可以不配置, 所有的bean可以都配置在上面的spring-mvc.xml 文件中。

有可能听说过 SpringMVC 是双容器设计。 Spring容器作为父容器,用于定义 Service、Mapper、Config等。 SpringMVC容器作为子容器, 用于定义 controller、 RequestMapping组件等与 SpringMVC相关的bean。

如果需要, 可以通过ContextLoaderListener去加载 Spring容器。默认的配置文件:WEB-INF/applicationContext.xml

 <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

自定义配置文件名:

  <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/applicationContext1.xml</param-value>
  </context-param>

路径支持: Ant-Style 风格。 如果需要指定多个,使用 ,(逗号)分割

DispatcherServlet初始化

DispatcherServlet 顾名思义是一个 Servlet。
Servlet的创建时机:

  • 当 Servvlet第一次被访问
  • 定义时, 配置了 <load-on-startup>1</load-on-startup>

在Servlet容器中(tomcat、jetty),启动时就会解析web.xml文件,并创建 <load-on-startup>1</load-on-startup>的Servlet, 调用init 方法

【Servlet】

public void init(ServletConfig config)

The servlet container calls the init method exactly once after instantiating the servlet. The init method must complete successfully before the servlet can receive any requests.

Servlet 容器会精确调用一次servlet的init 方法,用于执行一些初始化的操作。

DispatcherServlet的类结构:
在这里插入图片描述

DispatcherServlet是SpringMVC中的核心类,继承于Servlet,故在Servlet容器中会触发一次init方法。

调用链如下:
在这里插入图片描述

FrameworkServlet#initServletBean

核心代码,创建WebApplicationContext。

this.webApplicationContext = initWebApplicationContext()

【initWebApplicationContext】

// 从ServletContext 中获取父容器
WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());

// 创建容器, 传递父容器(可以为null)
WebApplicationContext wac = createWebApplicationContext(rootContext);

【createWebApplicationContext】创建容器

	// 默认返回 XmlWebApplicationContext.class
	Class<?> contextClass = getContextClass();
	// 实例化
	ConfigurableWebApplicationContext wac =
			(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	// 设置环境变量
	wac.setEnvironment(getEnvironment());
	// 设置父容器
	wac.setParent(parent);
	// 设置自定义的配置文件
	String configLocation = getContextConfigLocation();
	if (configLocation != null) {
		wac.setConfigLocation(configLocation);
	}
	// 配置其他属性 并 refresh 容器
	configureAndRefreshWebApplicationContext(wac);

【configureAndRefreshWebApplicationContext】刷新容器

	// 存储 ServletContext
	wac.setServletContext(getServletContext());
	// 存储,ServletConfig配置
	wac.setServletConfig(getServletConfig());
	wac.setNamespace(getNamespace());
	// 添加事件监听器
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
	// 加载 ServletContext中的环境变量,当然也会触发系统环境变量和系统属性的加载
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}
	
	// 执行ApplicationContextInitializer, 自定义容器处理
	applyInitializers(wac);
	// 刷新容器
	wac.refresh();

需要注意,上述添加了 事件处理器 ContextRefreshListener

ContextRefreshListener
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		FrameworkServlet.this.onApplicationEvent(event);
	}
}

当容器加载完成时, 就会发布 ContextRefreshedEvent 事件。 此时就会触发 Dispatcher的进一步初始化。

public void onApplicationEvent(ContextRefreshedEvent event) {
	onRefresh(event.getApplicationContext());
}
DispatcherServlet.onRefresh

DispatcherServlet.onRefresh(ApplicationContext context)

protected void onRefresh(ApplicationContext context) {
    // 初始化SpringMVC组件
	initStrategies(context);
}

initStrategies 方法是DispatcherServlet 成员变量初始化的核心代码。

	protected void initStrategies(ApplicationContext context) {
	    // 初始化form-data 中的文件上传
		initMultipartResolver(context);
		// 国际化
		initLocaleResolver(context);
		// 主题
		initThemeResolver(context);
		// 映射器
		initHandlerMappings(context);
		// 处理器
		initHandlerAdapters(context);
		// 异常处理器
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		// 视图解析器
		initViewResolvers(context);
		initFlashMapManager(context);
	}
初始化成员变量 - handlerMappings

执行 initStrategies中的方法会DispatcherServlet中的成员变量赋值。

我们以 HandlerMapping初始化为例, HandlerAdapterHandlerExceptionResolverViewResolver都是这样的处理逻辑。


	/** List of HandlerMappings used by this servlet. */
	@Nullable
	private List<HandlerMapping> handlerMappings;

对应的方法

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
      
		if (this.detectAllHandlerMappings) {
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			//......................
		}

		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		}
	}

detectAllHandlerMappings 默认为true, 代码会进入if分支。

private boolean detectAllHandlerMappings = true

if分支逻辑很简单, 从IOC容器中获取类型为HandlerMapping.class 的Bean集合。

默认实现

回想之前的项目开发,我们没有配置HandlerMapping为什么依旧可以处理请求呢? 这是因为SpringMVC提供了默认HandlerMapping。

if (this.handlerMappings == null) {
	this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}

当在IOC容器中获取不到类型为HandlerMapping 的bean时,就会走默认的处理逻辑。

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		String key = strategyInterface.getName();
		String value = defaultStrategies.getProperty(key);
		//...... 创建实例并返回	
}

defaultStrategies 又是什么东西啊?

private static final Properties defaultStrategies;

看定义发现是Properties , 所以defaultStrategies 会加载一个properties配置文件。

	ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
	
	defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);

以下为截取的部分数据:

  • key: org.springframework.web.servlet.HandlerMapping,
  • value 是 HandlerMapping 字符串,多个逗号隔开。
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

看到这种全限定的类名,我们应该可以猜到, 可以通过Class.forName(“”) , cls.newInstance() 创建实例。

但是上面方式的创建有一个问题就是,创建的对象不受Spring IOC容器的管理,这样如果存在初始化方法也需要手动触发。

SpringMVC 采用的方式是将这些Bean的创建交给IOC 容器,故调用AutowireCapableBeanFactory的createBean 方式创建实例。 创建Bean实例的流程特别复杂,就不展开了。

Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
	return context.getAutowireCapableBeanFactory().createBean(clazz);
}
初始化成员变量 - multipartResolver

multipartResolver 、localeResolver、themeResolver 这些初始化逻辑相同。从容器中获取 指定名称指定类型的bean。

	private void initMultipartResolver(ApplicationContext context) {
		try {
			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
		}
		catch (NoSuchBeanDefinitionException ex) {
			this.multipartResolver = null;
		}
	}
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";

如果容器中没有这样的bean,没有默认的处理方式,即不支持 主题、国际化、多媒体资源的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值