Spring作为一个优秀的web框架,其运行是基于Tomcat的。在我们前面的讲解中,Spring的驱动都是使用的ClassPathXmlApplicationContext
,并且都是直接在main方法中启动的,但是在Tomcat容器中,我们是无法使用main方法的,因而其驱动方式必然与我们测试时不一样。Tomcat是一个基于Servlet规范的web容器,而Spring则提供了对Servlet规范的支持,其DispatcherServlet
则是Servlet规范的具体实现。因而在web开发过程中,当我们启动Tomcat容器时其会根据Servlet规范启动Spring实现的DispatcherServlet
,这样也就驱动了Spring的运行。本文主要从源码的角度讲解Spring在web容器中是如何初始化的。
1. web.xml配置
在配置web容器时,我们都会配置一个web.xml,而在配置web.xml时,最主要的两个组件就是ContextLoaderListener
和DispatcherServlet
的配置。如下是一个典型的web.xml文件的配置:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>myservlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myservlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
这里ContextLoaderListener
的作用是对对Servlet Context的行为进行监听,其实现了ServletContextListener
接口,这个接口声明如下:
public interface ServletContextListener extends EventListener {
// 用于在Servlet Context初始化事前执行
default public void contextInitialized(ServletContextEvent sce) {}
// 用于在Servlet Context被销毁之后执行
default public void contextDestroyed(ServletContextEvent sce) {}
}
这里ServletContextListener
是Servlet规范中提供的一个接口,该接口中的contextInitialized()
会在servlet context初始化之前执行,而contextDestroyed()
方法则会在servlet context被销毁之后执行。Spring提供的ContextLoaderListener
对这两个方法都进行了实现。实际上,在web.xml中指定的contextConfigLocation参数的解析就是在ContextLoaderListener.contextInitialized()
方法中解析的,也就是说Spring对于bean的创建实际上是在Servlet Context初始化之前就已经完成了。
web.xml中配置的DispatcherServlet
则是Servlet规范中HttpServlet的一个具体实现,实现了该接口的之后该类就具有处理web请求的能力了。这里可以看到,DispatcherServlet
配置拦截的url是'/',也就是说所有的web请求都会经过DispatcherServlet
,而对于具体的url的处理,实际上是在DispatcherServlet
中进行分发的。这也就是Spring为什么只需要配置一个Servlet的原因。
关于DispatcherServlet
的配置这里不得不提的是,我们得为其提供一个myservlet-servlet.xml的配置文件,用于只为当前servlet提供Spring的一些基本配置。这里该文件的命名必须按照servlet名称-servlet.xml
这种格式进行,由于我们的servlet的名称为myservlet,因而配置文件名必须为myservlet-servlet.xml。如果使用者需要自定义文件名,可以在当前servlet中使用init-param
标签进行配置,如:
<servlet>
<servlet-name>myservlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/myservlet-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
另外,对于配置的myservlet-servlet.xml文件的初始化,是在DispatcherServlet.init()
方法中进行的。这里需要注意的是,myservlet-servlet.xml完完全全是一个独立的Spring配置文件,我们可以在其中声明Spring的bean,并且注册到Spring容器中。
在web.xml中我们提到了两个Spring的配置文件,一个是我们常用的applicationContext.xml,另一个专属于某个Servlet的myservlet-servlet.xml。两个配置文件的初始化分别是由ServletContextListener.contextInitialized()
方法和GenericServlet.init()
方法进行的。这两个方法都是Servlet规范中提供的初始化方法,两个方法分别会初始化两个Spring容器,这两个容器中applicationContext.xml对应的容器会作为myservlet-servlet.xml初始化的容器的父容器而存在,因而在myservlet-servlet.xml的容器中,我们是可以使用任何在applicationContext.xml中声明的bean的,但是反过来则不行。在处理具体请求的时候,我们所使用的Spring容器其实一直都是myservlet-servlet.xml声明而来的。
2. ContextLoaderListener初始化
对于ContextLoaderListener,其主要是用于初始化我们常用的applicationContext.xml的。如下是其源码:
public class ContextLoaderListener extends ContextLoader
implements ServletContextListener {
// 初始化Spring容器
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
// 销毁Spring容器
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
可以看到,这里对于Spring容器的初始化是委托给了initWebApplicationContext()
方法进行的,如下是该方法的源码:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 如果当前已经初始化过一个web application context则抛出异常,这样可以保证一个web容器中
// 只会有一个web application context
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 {
if (this.context == null) {
// 通过servlet配置创建一个WebApplicationContext对象
this.context = createWebApplicationContext(servletContext);
}
// 这里在createWebApplicationContext()方法中会保证创建的WebApplicationContext本质上是
// ConfigurableWebApplicationContext类型的,因而这里进行类型判断的时候是能够进入if分支的
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac =
(ConfigurableWebApplicationContext) this.context;
// 如果当前WebApplicationContext没有初始化过就对其进行初始化
if (!cwac.isActive()) {
// 如果当前WebApplicationContext没有父ApplicationContext,则通过
// loadParentContext()方法加载一个,该方法实际上是一个空方法,这里提供
// 出来只是为了方便用户进行容器属性的自定义,因为父容器的内容会继承到子容器中
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 对WebApplicationContext进行配置,并且调用其refresh()方法初始化
// 配置文件中配置的Spring的各个组件
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 初始化完成之后将当前WebApplicationContext设置到ServletContext中
servletContext.setAttribute(WebApplicationContext
.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// 设置当前WebApplicationContext的类加载器
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;
}
}
可以看到,对于WebApplicationContext的初始化,Spring首先会根据配置文件配置创建一个WebApplicationContext对象,然后判断该对象是否初始化过,如果没有,则对其进行配置并且初始化。这里我们首先看看Spring是如何创建WebApplicationContext对象的:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 读取配置文件中配置的实现了WebApplicationContext接口的类
Class<?> contextClass = determineContextClass(sc);
// 判断读取到的类是否实现了ConfigurableWebApplicationContext接口,如果没实现则抛出异常
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class ["
+ contextClass.getName() + "] is not of type ["
+ ConfigurableWebApplicationContext.class.getName() + "]");
}
// 通过反射实例化WebApplicationContext对象
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
// 这个方法的主要作用在于读取配置文件中配置的实现了WebApplicationContext接口的类,
// 从而作为WebApplicationContext容器。这里读取配置文件的方式有两种:①读取web.xml中配置的
// contextClass属性,如果存在则将其作为WebApplicationContext容器;②读取Spring提供的
// ContextLoader.properties属性文件中配置的WebApplicationContext容器。
protected Class<?> determineContextClass(ServletContext servletContext) {
// 读取用户在web.xml中使用contextClass属性自定义的WebApplicationContext容器,
// 如果不为空,则直接返回
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName,
ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
} else {
// 如果用户没有自定义WebApplicationContext,则通过defaultStrategies读取
// ContextLoader.properties属性文件中配置的WebApplicationContext,
// 这里读取到的具体实现类就是XmlWebApplicationContext
contextClassName = defaultStrategies
.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName,
ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
这里讲到,我们可以在web.xml中配置自定义的WebApplicationContext,具体的配置方式就是在web.xml中配置如下属性:
<context-param>
<param-name>contextClass</param-name>
<param-value>mvc.config.MyXmlWebApplicationContext</param-value>
</context-param>
通过这种方式我们就可以实现自定义的WebApplicationContext。对于Spring提供的默认WebApplicationContext实现,其是通过defaultStrategies这个属性读取的,这个属性的初始化是在ContextLoader(ContextLoaderListener继承了该类)中使用static代码块进行初始化的,读者可自行查阅。
在创建了WebApplicationContext对象之后,Spring会对其进行配置和各个组件的初始化,如下是ContextLoader.configureAndRefreshWebApplicationContext()
方法的具体实现:
protected void configureAndRefreshWebApplicationContext(
ConfigurableWebApplicationContext wac, ServletContext sc) {
// 判断当前WebApplicationContext是否具有统一的id,如果没有,首先会从web.xml中读取,
// 具体的使用contextId属性进行制定,该属性的配置方式与上面的contextClass一致,如果没有,
// 则通过默认规则声明一个
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 获取web.xml中配置的contextConfigLocation属性值,这里也就是我们前面配置的
// applicationContext.xml,在后面调用refresh()方法时会根据xml文件中的配置
// 初始化Spring的各个组件
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// 获取当前Spring的运行环境,并且初始化其propertySources
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 这里customizeContext()方法是一个空方法,供给用户自定义实现ContextLoaderListener时
// 对WebApplicationContext进行自定义
customizeContext(sc, wac);
// 这里refresh()方法用于读取上面声明的配置文件,并且初始化Spring的各个组件
wac.refresh();
}
关于WebApplicationContext的配置和初始化,这里主要分为了四个步骤:①为当前WebApplicationContext声明一个id,用于对其进行唯一标识;②读取web.xml中配置的Spring配置文件的位置;③初始化propertySources;④读取Spring配置文件中的内容,并且实例化Spring的各个组件。这里需要说明的是,对于Spring各个组件的初始化,调用的是ConfigurableWebApplicationContext.refresh()
方法,这个方法我们前面讲解Spring bean注册解析时已经讲解了,读者可以翻阅Spring Bean注册解析(一)和Spring Bean注册解析(二)。
在ConfigurableWebApplicationContext.refresh()
方法调用完成之后,Spring配置文件中的各项配置就都已经处理完成。如此,ContextLoaderListener
的初始化工作也就完成。
3. DispatcherServlet的初始化
对于DispatcherServlet的初始化,这里需要注意的是,在web.xml中我们配置了load-on-startup
标签,配置了该标签就表示当前Servlet的初始化方法会在web容器启动完成后调用,也就是这里的DispatcherServlet.init()
方法。我们首先看看该方法的源码:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 读取在web.xml中通过init-param标签设置的属性,如果没有配置,这里pvs就会是empty的
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),
this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 注册Resource对象对应的PropertyEditor
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader =
new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class,
new ResourceEditor(resourceLoader, getEnvironment()));
// 初始化BeanWrapper对象,这里是一个空方法,供给使用者对BeanWrapper进行自定义处理
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;
}
}
// 初始化当前DispatcherServlet的各项配置
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
可以看到,这里对DispatcherServlet的初始化主要分为两个步骤:①判断当前Servlet中使用使用init-param标签自定义了属性,如果定义了,则将其设置到BeanWrapper中;②初始化DispatcherServlet。这里我们继续阅读initServletBean()
的源码:
@Override
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 {
// 初始化当前servlet配置的Spring配置
this.webApplicationContext = initWebApplicationContext();
// 这里initFrameworkServlet()方法是一个空方法,供给用户对当前servlet对应的Spring容器
// 进行自定义的处理
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");
}
}
这里initServletBean()
除了进行一些日志记录以外,主要工作还是委托给了initWebApplicationContext()
方法进行,我们这里直接阅读该方法的源码:
protected WebApplicationContext initWebApplicationContext() {
// 获取在ContextLoaderListener中初始化的Spring容器,并且将其作为当前servlet对应
// 的容器的父容器,这样当前servlet容器就可以使用其父容器中的所有内容了
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 如果当前Servlet对应的WebApplicationContext不为空,并且其未被初始化,则对其进行初始化
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac =
(ConfigurableWebApplicationContext) wac;
// 判断当前WebApplicationContext是否已初始化过,没有则进行初始化
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
// 初始化当前WebApplicationContext
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 如果wac为空,则说明当前servlet对应的WebApplicationContext是空的,
// 这里会通过当前servlet配置的contextAttribute属性查找一个自定义的
// WebApplicationContext,将其作为当前servlet的容器
wac = findWebApplicationContext();
}
if (wac == null) {
// 如果用户没有自定义WebApplicationContext,则创建一个,并且对其进行初始化
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 这里的onRefresh()方法并不是初始化Spring配置文件中的bean的,
// 而是用于初始化Spring处理web请求相关的组件的,如RequestMappingHandlerMapping等
onRefresh(wac);
}
if (this.publishContext) {
// 将当前WebApplicationContext对象设置到ServletContext中
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '"
+ getServletName() + "' as ServletContext attribute with name ["
+ attrName + "]");
}
}
return wac;
}
这里默认情况下,DispatcherServlet中是不存在已经初始化过的WebApplicationContext的,因而最终还是会调用createWebApplicationContext()方法进行初始化,在初始化完成之后就会初始化Spring处理web请求的相关组件。我们首先看createWebApplicationContext()方法的实现:
protected WebApplicationContext createWebApplicationContext(@Nullable
ApplicationContext parent) {
// 读取web.xml中配置的contextClass属性,将其作为当前servlet的WebApplicationContext
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName()
+ "' will try to create custom WebApplicationContext context of class '"
+ contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
// 保证用户定义的WebApplicationContext对象是ConfigurableWebApplicationContext类型的
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");
}
// 实例化WebApplicationContext对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
// 设置当前的运行环境
wac.setEnvironment(getEnvironment());
// 将ContextLoaderListener中初始化的WebApplicationContext作为当前
// WebApplicationContext的父容器
wac.setParent(parent);
// 获取当前servlet配置的contextConfigLocation
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 读取当前WebApplicationContext配置的Spring相关的bean,并进行初始化
configureAndRefreshWebApplicationContext(wac);
return wac;
}
这里对当前servlet的WebApplicationContext的初始化过程其实比较简单,其中最主要需要注意的有两点:①会将ContextLoaderListener初始化的WebApplicationContext作为当前WebApplicationContext的父容器;②在获取当前configLocation的时候,如果没有设置,则使用"servlet名称-servlet.xml"的方式读取。
4. Spring web九大组件初始化
在第三点最后,我们讲到,初始化servlet对应的容器之后,其会调用onRefresh()方法初始化Spring web相关的组件,该方法的具体实现在DispatcherServlet.onRefresh()
方法中,这里我们直接阅读该方法的源码:
@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);
}
这里对Spring的九大组件的实例化方式都比较统一,关于这九大组件的具体细节我们后面会依次进行讲解。这里我们主要讲解其初始化方式。关于这九大组件,其实例化方式可分为两类:
- 通过制定的bean名称在Spring容器中读取对应的bean,如果不存在则使用默认的类来初始化;
- 通过参数配置控制是在Spring容器中读取指定实现指定接口的所有bean,还是读取Spring容器中指定名称的bean,如果这两种方式都无法读取到对应的bean,则读取Spring配置文件中配置的默认的bean。
对于第一种实例化方式,我们这里以LocaleResolver的初始化为例进行讲解,如下是initLocaleResolver()方法的源码:
private void initLocaleResolver(ApplicationContext context) {
try {
// 这里LOCALE_RESOLVER_BEAN_NAME的值为localeResolver,也就是说用户如果
// 需要自定义的LocaleResolver,那么在声明该bean是,其名称必须为localeResolver
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME,
LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
} catch (NoSuchBeanDefinitionException ex) {
// 如果Spring容器中没有配置自定义的localeResolver,则通过默认策略实例化对应的bean
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '"
+ LOCALE_RESOLVER_BEAN_NAME
+ "': using default [" + this.localeResolver + "]");
}
}
}
对于第二种方式,我们这里以HandlerMapping的实例化为例进行讲解,如下是initHandlerMappings()方法的实现原理:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 检查是否配置了获取Spring中配置的所有HandlerMapping类型对象,是则进行读取,并且按照
// 指定的排序规则对其进行排序,否则就从Spring中读取名称为handlerMapping的bean,
// 并将其作为指定的bean
if (this.detectAllHandlerMappings) {
// 从Spring容器中读取所有的实现了HandlerMapping接口的bean
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// 对获取到的HandlerMapping进行排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
} else {
try {
// 获取Spring容器中名称为handlerMapping的bean
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME,
HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
} catch (NoSuchBeanDefinitionException ex) {
// 忽略当前异常
}
}
if (this.handlerMappings == null) {
// 如果上述方式没法获取到对应的HandlerMapping,则使用默认策略获取对应的HandlerMapping
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '"
+ getServletName() + "': using default");
}
}
}
上述两种初始化Spring web组件的方式中都涉及到一个获取默认的bean的方法,该方法实际上是从Spring提供的配置文件指定对应的bean的Class,在读取该文件之后会对其进行实例化,然后返回。对于getDefaultStrategies()方法的实现原理,其实比较简单,我们这里主要给大家展示Spring提供的组件的配置文件的内容,该配置文件的名称为DispatcherServlet.properties
,如下是该文件的内容:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
可以看到,这里的配置文件的key就是对应的接口的全路径名,这也就是getDefaultStrategies()方法第二个参数传入的是Class对象的原因,而value就是该接口对应的实现类,可以有多个。
5. 小结
本文首先讲解了web.xml文件的配置方式,并且着重讲解了该文件中各个配置的意义,接着依次在源码的层面对web.xml中配置的各个组件的初始化方式进行了讲解。如果大家想学习以上路线内容,在此我向大家推荐一个架构学习交流群。交流学习群号874811168 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多