在这里插入图片描述

1:初始化入口在哪里?

1.1:熟悉的DispatcherServlet

org.springframework.web.servlet.DispatcherServlet是springmvc的入口servlet,继承了javax.http.HttpServlet,继承关系如下图:
在这里插入图片描述我们知道,按照servlet规范,web容器加载servlet时会调用其init(ServletConfig)方法进行,而org.springframework.web.servlet.DispatcherServlet的init(ServletConfig)方法是在其父类javax.servlet.GenericServlet类中,源码如下:

javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
public void init(ServletConfig config) throws ServletException {
	this.config = config;
	// <1>
	this.init();
}

<1>处会执行到org.springframework.web.servlet.HttpServletBean#init,源码如下:

org.springframework.web.servlet.HttpServletBean#init
public final void init() throws ServletException {
	// 2023-04-03 13:03:38
	initServletBean();
}

2023-04-03 13:03:38处就比较关键了,负责创建springMVC的相关bean,并创建springMVC的IOC容器 web applicationcontext,方法initServletBean();是一个模板方法,其逻辑实现是在org.springframework.web.servlet.FrameworkServlet中,源码如下:

@Override
protected finalvoid initServletBean() throws ServletException {
	...
	try {
		// 初始化WebApplicationContext
		this.webApplicationContext = initWebApplicationContext();
		// 模板方法,子类使用
		initFrameworkServlet();
	}
    ...
}

其中代码this.webApplicationContext = initWebApplicationContext();就是负责初始化Servlet WebApplicationContext的,也就是我们要找的入口程序了。接下来就是org.springframework.web.servlet.DispatcherServlet,主要负责初始化springmvc的九大组件

1.2:入口init(ServletConfig)

web容器在初始化servlet时会调用方法init(ServletConfig),在javax.servlet.HttpServlet中提供具体实现,如下:

javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
public void init(ServletConfig config) throws ServletException {
	this.config = config;
	// 2023-04-03 13:12:54
	this.init();
}

接着2023-04-03 13:12:54调用的初始化方式init是在org.springframework.web.servlet.HttpServletBean中,源码如下:

org.springframework.web.servlet.HttpServletBean#init
public final void init() throws ServletException {
   // <1>
	PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
	if (!pvs.isEmpty()) {
		try {
			// <2>
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			// <3>
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
		}
	}
	// <4>
	initServletBean();
}

<1>处代码是将ServletConfig信息封装到ServletConfigPropertyValues中,这是一个静态内部类,<2>处代码是将org.springframework.web.servlet.DispatcherServlet封装为org.springframework.beans.BeanWrapper,是spring提供的操作java bean的一个工具类,接口定义如下:

public interface BeanWrapper extends ConfigurablePropertyAccessor {
	void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
	int getAutoGrowCollectionLimit();
	Object getWrappedInstance();
	Class<?> getWrappedClass();
	PropertyDescriptor[] getPropertyDescriptors();
	PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
}

其唯一的实现类是org.springframework.beans.BeanWrapperImpl<3>处是注册属性编辑器。<4>处是一个模板方法,调用子类,这里的具体实现在子类org.springframework.web.servlet.FrameworkServlet类中。下面再简单介绍下<1>处源码,相关注释已经直接在源码中添加:

private static class ServletConfigPropertyValues extends MutablePropertyValues {
	public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
			throws ServletException {
		// requiredProperties为强制通过init-param配置的参数,通过
		// org.springframework.web.servlet.HttpServletBean#addRequiredProperty进行添加
		Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
				new HashSet<>(requiredProperties) : null);

		Enumeration<String> paramNames = config.getInitParameterNames();
		while (paramNames.hasMoreElements()) {
			String property = paramNames.nextElement();
			Object value = config.getInitParameter(property);
			addPropertyValue(new PropertyValue(property, value));
			// 如果是存在强制缺失属性,则将当前属性从强制缺失集合中移除,代表已经不缺失了,这里不用管是否包含,直接移除即可
			if (missingProps != null) {
				missingProps.remove(property);
			}
		}

		// Fail if we are still missing properties.
		// 如果仍然有缺失属性则抛出javax.servlet.ServletException
		if (!CollectionUtils.isEmpty(missingProps)) {
			throw new ServletException(
					"Initialization from ServletConfig for servlet '" + config.getServletName() +
					"' failed; the following required properties were missing: " +
					StringUtils.collectionToDelimitedString(missingProps, ", "));
		}
	}
}

目前我没有找到这个requiredProperties有什么使用场景,如果是哪位朋友,在工作中用到了,欢迎给我留言告知,不胜感激。

2:Spring Servlet WebApplicationContext初始化

2.1: initServletBean()

org.springframework.web.servlet.FrameworkServlet实现了在org.springframework.web.servlet.HttpServletBean中定义的模板方法initServletBean,因此加载org.springframework.web.servlet.DispatcherServlet时会调用到该方法,该方法如下:

protected final void initServletBean() throws ServletException {
	try {
	    // <1>
		this.webApplicationContext = initWebApplicationContext();
		// <2>
		initFrameworkServlet();
	}
}

<1>处代码就是用来初始化Spring Servlet WebApplicationContext容器对象的。<2>处是一个模板方法,定义如下:

protected void initFrameworkServlet() throws ServletException {
}

但是目前并没有子类重写该方法,可以先忽略之。接下来详细讲解<1>处代码。

2.2: initWebApplicationContext()

该方法负责对Spring Servlet WebApplicationContext进行初始化,并设置到web容器的ServletContext上下文对象中,源码如下:

protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;
	// <1>
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		wac = this.webApplicationContext;
		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);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// <2>
	if (wac == null) {
		wac = findWebApplicationContext();
	}
	// <3>
	if (wac == null) {
		wac = createWebApplicationContext(rootContext);
	}
	// <4>
	if (!this.refreshEventReceived) {
		synchronized (this.onRefreshMonitor) {
			onRefresh(wac);
		}
	}
	// <5>
	if (this.publishContext) {
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}
	return wac;
}

<1>处代码当使用的是org.springframework.web.servlet.FrameworkServlet的带WebApplicationContext参数的构造函数的时候,源码如下:

public FrameworkServlet(WebApplicationContext webApplicationContext) {
	this.webApplicationContext = webApplicationContext;
}

或者是当自己是spring容器中的bean时,因为实现了org.springframwork.context.ApplicationContextAware接口,因此会回调setApplicationContext(ApplicationContext applicationContext)方法进行设置,但是一般不会存在这种情况。这两种情况都不是核心流程,可以忽略,所以<1>处代码暂时不用深究。<2>处是源码如下:

protected WebApplicationContext findWebApplicationContext() {
	String attrName = getContextAttribute();
	if (attrName == null) {
		return null;
	}
	WebApplicationContext wac =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
	if (wac == null) {
		throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
	}
	return wac;
}

getContextAttribute()就是获取名称为contextAttributeinit-param参数,配置方式如下:

<init-param>
	<param-name>contextAttribute</param-name>
	<param-value>contextAttributeXXXXX</param-value>
</init-param>

配置后以值为key去ServletContext中获取对应的WebApplicationContext,如果没有的话,则会抛出类似如下的错误:

java.lang.IllegalStateException: No WebApplicationContext found: initializer not registered?
at org.springframework.web.servlet.FrameworkServlet.findWebApplicationContext(FrameworkServlet.java:679)
at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:620)
...

<3>处代码就是本文分析的核心,是负责创建Spring Servlet WebApplicationContext容器对象的,单起一部分讲解。<4>处代码onRefresh也是一个模板方法,具体实现在org.springframework.web.servlet.DispatcherServlet中,是用来初始化spring mvc的九大组件的,源码如下:

@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

暂时不做深究。 <5>处代码通过publishContext变量判断是否需要将创建的Spring Servlet WebApplicationContext设置到web容器的ServletContext的上下文中,默认为true,也可以通过init-paramweb.xml中设置,如下:

<init-param>
	<param-name>publishContext</param-name>
	<param-value>false</param-value>
</init-param>

一般不会进行设置,除非有特别的需求,目前俺还没有遇到过需要设置的,如果哪位同学遇到过这种场景,跪求留言分享。接下来详细分析下<3>处代码。

2.3: createWebApplicationContext(rootContext)

源码如下:

protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
	return createWebApplicationContext((ApplicationContext) parent);
}

最终调用参数为ApplicationContext类型的重载方法:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
   // <1>
	Class<?> contextClass = getContextClass();
	// <2>
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException(
				"Fatal initialization error in servlet with name '" + getServletName() +
				"': custom WebApplicationContext class [" + contextClass.getName() +
				"] is not of type ConfigurableWebApplicationContext");
	}
	// <3>
	ConfigurableWebApplicationContext wac =
			(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	wac.setEnvironment(getEnvironment());
	// <4>
	wac.setParent(parent);
	// <5>
	String configLocation = getContextConfigLocation();
	if (configLocation != null) {
		wac.setConfigLocation(configLocation);
	}
	// <6>
	configureAndRefreshWebApplicationContext(wac);
	return wac;
}

<1>处代码获取要创建ApplicationContext的类型,这里默认是org.springframework.web.context.support.XmlWebApplicationContext.class类型,一般不会修改。<2>处判断contextClass是否为org.springframework.web.context.ConfigurableApplicationContext类型,如果不是的话则抛出org.springframework.context.ApplicationContextException异常。<3>处通过Class对象创建实例对象。<4>处设置父容器为Root WebApplicationContext,关于ROOT WebApplicationContext可以参考这里<5>处获取在web.xml中配置的contextConfigLocation,可能如下配置:

<servlet>
	<servlet-name>letsGO</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/letsGO-servlet.xml</param-value>
	</init-param>
</servlet>

则这里获取的值就是WEB-INF/letsGO-servlet.xml,即springMVC的配置文件。<6>处代码是对Spring Servlet WebApplicationContext进行配置和初始化,接下来对这一部分进行详细说明。

2.4: configureAndRefreshWebApplicationContext(wac)

源码如下:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	// <1>
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		if (this.contextId != null) {
			wac.setId(this.contextId);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
		}
	}
	// <2>
	wac.setServletContext(getServletContext());
	// <3>
	wac.setServletConfig(getServletConfig());
	// <4>
	wac.setNamespace(getNamespace());
	// <5>
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}
	// <6>
	postProcessWebApplicationContext(wac);
	applyInitializers(wac);
	// <7>
	wac.refresh();
}

<1>处是设置容器id,如果是默认策略生成的id则分为两种情况,第一种是用户配置了contextId,配置方式为在web.xml给ServletContext配置参数即context-param,可能为如下配置:

<context-param>
	<param-name>contextId</param-name>
	<param-value>dongsir-root-web-application-context-id</param-value>
</context-param>

则此处获取的值就是dongsir-root-web-application-context-id,若没有配置则生成一个id并设置。<2>处设置web容器的ServletContext对象。<3>处设置ServletConfig对象,<4>处设置命名空间,<5>通过注册SourceFilteringListener完成当<7>处代码执行完毕后对ContextRefreshListener的代理调用,SourceFilterListener代码如下:

public class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {
	private final Object source;

	@Nullable
	private GenericApplicationListener delegate;
	protected void onApplicationEventInternal(ApplicationEvent event) {
		if (this.delegate == null) {
			throw new IllegalStateException(
					"Must specify a delegate object or override the onApplicationEventInternal method");
		}
		this.delegate.onApplicationEvent(event);
	}
}

其中private GenericApplicationListener delegate就是被代理的ContextRefreshListener,最终调用代码onApplicationEventInternal完成被代理监听器的调用。<6>处是创建Spring Servlet WebApplicationContext完毕后的后置处理模板方法,目前没有子类实现改后置处理方法。<7>处是刷新容器。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐