springmvc–启动流程
文章目录
- springmvc--启动流程
- 1 使用`springmvc`搭建一个简单的`web`应用
- 2 `XmlWebApplicationContext`创建过程
- 3 `Servlet`初始化过程
- 3.1 `ServletConfigPropertyValues`
- 3.2 `GenericServlet`类实现`init()`方法
- 3.3 `HttpServletBean`类实现重载的`init()`方法
- 3.4 `FrameworkServlet`类实现`initServletBean()`方法
- 3.5 初始化`springmvc`的上下文环境
- 3.5.1 初始化`web`上下文
- 3.5.1.1 设置 `web`上下文的命名空间
- 3.5.1.2 `ContextRefreshListener`上下文刷新监听器
- 3.5.1.3 `SourceFilteringListener`,包装监听器
- 3.5.1.4 获取`web`应用上下文的环境对象
- 3.5.1.5 将`ServletContext`的配置注册到环境中
- 3.5.1.6 `postProcessWebApplicationContext()`方法,在执行`refresh()`方法前增强`web`上下文
- 3.5.1.7 实例化`ApplicationContextInitializer`接口对象,并调用接口的`initialize()`方法增强`web`上下文
- 3.5.2 获取注册到`ServletContext`中的`web`上下文
- 3.5.3 创建一个默认的`web`上下文
- 4 父子容器
- 5 `springmvc`默认的`9`大组件的初始化策略
1 使用springmvc
搭建一个简单的web
应用
我们使用servlet3.0+
以上的版本,干掉web.xml
文件,不知道你们有没有和我一样的,每次新建项目还得创建一个webapp
目录,就感到特别烦。servlet3.0+
添加了SCI
支持,通过继承一个接口替换web.xml
。
1.1 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
web
应用程序只需要导入这两个包即可,其中第二个包javax.servlet-api
,设定了它的scope
作用域,表示不会将该依赖打包到war
包中,因为tomcat
中已经包含了javax.servlet-api
这个包,如果这里再打包进去的话,就会造成依赖冲突。
1.2 配置一个可编程的Servlet container
public class MyWebApplicationInitializer implements WebApplicationInitializer {
public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {
//创建web应用的上下文
XmlWebApplicationContext xmlWebApplicationContext = new XmlWebApplicationContext();
//设置配置文件路径(配置文件在类路径下,即resources根目录下)
xmlWebApplicationContext.setConfigLocation("classpath:spring-mvc.xml");
/**
* 创建一个DispatcherServlet
* 了解过springmvc的都知道,它就是一个servlet,这个servlet处理所有请求
*/
DispatcherServlet dispatcherServlet = new DispatcherServlet(xmlWebApplicationContext);
//在servlet上下文中注册DispatcherServlet
ServletRegistration.Dynamic dynamic = servletContext.addServlet(
"dispatcherServlet", dispatcherServlet);
//设置DispatcherServlet的顺序
dynamic.setLoadOnStartup(1);
//设置拦截路径
dynamic.addMapping("/*");
}
}
1.3 spring-mvc.xml
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--只扫描controller-->
<context:component-scan base-package="cn.lx.spring.v2.controller"></context:component-scan>
<!--开启springmvc配置,相当于@EnableWebMvc注解-->
<mvc:annotation-driven>
<!--
配置自定义的消息转换器
需要导入jackson的包jackson-module-parameter-names和
jackson-dataformat-xml
-->
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
</beans>
1.4 controller
控制器
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/user")
public User getUser(){
User user = new User();
user.setAge(12);
user.setName("lixiang");
user.setPassword("1222223");
return user;
}
}
浏览器访问http://localhost:8080/spring-test/test/user
,得到如下结果
<User>
<name>lixiang</name>
<password>1222223</password>
<age>12</age>
</User>
到此为止,一个简单的
web
应用程序就创建好了。但是又出现了新的问题:浏览器为什么显示的是XML
格式的数据呢?我们以前不是显示json
格式的数据吗?不要着急,我们接着往下看。
2 XmlWebApplicationContext
创建过程
借助原生servlet3.0
增加的SCI
启动实现
2.1 ServletContainerInitializer
接口
首先来说一下原生servlet3.0
增加的SCI
启动模式,不知道到直接去servlet
官网找教程。
SCI
说起来也很简单,就是web
应用程序在启动的开始阶段自动回调ServletContainerInitializer
接口实现类的onStartup()
方法。- 如果你想使用这种模式,必须做到一下几点
- 必须在
META-INF/services
创建一个文件javax.servlet.ServletContainerInitializer
,文件内容为实现ServletContainerInitializer
接口的类的完全限定名。- 如果在实现
ServletContainerInitializer
接口的类上标注@HandlesTypes
注解,表示在调用onStartup()
方法的时候获取到@HandlesTypes
注解指定的类的clazz
对象。
public interface ServletContainerInitializer {
/**
* 启动的时候自动回调实现类的这个方法
* @param c 所有ServletContainerInitializer实现类@HandlesTypes注解指定的类
* @param ctx servlet上下文
*/
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
2.2 SpringServletContainerInitializer
(2.1
接口的实现类)
springmvc
定义的ServletContainerInitializer
接口的实现,spring
为什么要定义这个类呢?
打开
spring-web
包的源码,该包中存在一个META-INF/servicesjavax.servlet.ServletContainerInitializer
文件,而再看它的内容org.springframework.web.SpringServletContainerInitializer
,这不就是spring
定义的这个类的完全限定名嘛。也就是说web
应用程序在启动的开始阶段会自动回调SpringServletContainerInitializer
类的onStartup()
方法
我们来看一下它的源码
/**
* 这里使用了@HandlesTypes注解,那么就会将该注解指定的类的实现类注入到onStartup()的第一个参数中
*/
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* 启动阶段自动回调该方法
* webAppInitializerClasses中存放了@HandlesTypes注解指定的类的实现类的clazz对象
* servletContext是servlet上下文
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
//排除掉接口和抽象类
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//反射实例化
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
//根据@Order注解排序
AnnotationAwareOrderComparator.sort(initializers);
//遍历,调用onStartup()方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
在这个
web
程序中webAppInitializerClasses
集合中有5
个clazz
对象,它们都是WebApplicationInitializer
的子类,MyWebApplicationInitializer
这是我们自己定义的,另外4
个类都是抽象类(AbstractContextLoaderInitializer
、AbstractDispatcherServletInitializer
、AbstractAnnotationConfigDispatcherServletInitializer
、AbstractReactiveWebInitializer
),那么最终就只实例化了我们自己定义的MyWebApplicationInitializer
,然后调用这个对象的onStartup()
方法。
spring
这样设计的目的是简化配置,用户不需要再配置META-INF/servicesjavax.servlet.ServletContainerInitializer
文件了
3 Servlet
初始化过程
下面是原生的servlet
接口,我们只需要关注两个方法init()
、service()
方法,一个用来初始化servlet
,一个用来处理请求。
public interface Servlet {
/**
* servlet初始化方法
* 在web应用程序启动的时候,tomcat会自动调用所有servlet的初始化方法,
*/
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
//servlet处理请求的方法
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
//servlet销毁方法
public void destroy();
}
springmvc
程序只向ServletContext
中注册了一个Servlet
,那就是DispatcherServlet
,下面是它的类图
3.1 ServletConfigPropertyValues
这是HttpServletBean
类的一个静态嵌套类,下面是它的类图
这个
MutablePropertyValues
类以前是不是见到过?在我的
spring--7--类型转换PropertyEditor
这篇文章中提到:spring
在属性填充阶段要填充的属性值都保存在这个结构中,所以在那篇文章的开头(2.1
章节)我们就讲了MutablePropertyValues
,不知道的可以去看一下,这里的重点是ServletConfigPropertyValues
类。
/**
* PropertyValues implementation created from ServletConfig init parameters.
*/
private static class ServletConfigPropertyValues extends MutablePropertyValues {
/**
* Create new ServletConfigPropertyValues.
* @param config the ServletConfig we'll use to take PropertyValues from
* @param requiredProperties set of property names we need, where
* we can't accept default values
* @throws ServletException if any required properties are missing
* 构造方法
*/
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
//requiredProperties表示需要用户手动赋值的属性,不能使用默认值
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
/**
* 得到servlet的初始化参数
* 其实我们在web.xml文件的servlet标签中配置的init-param标签的内容
*/
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
/**
* 得到属性名和属性值之后,构建一个PropertyValue,保存到当前对象中
* 一个PropertyValue对象,代表一项配置
*/
addPropertyValue(new PropertyValue(property, value));
//移除掉必须初始化的属性
if (missingProps != null) {
missingProps.remove(property);
}
}
//用户没有为某个必须初始化的属性赋值,抛异常
// Fail if we are still missing properties.
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
}
总结一下这个类的作用:
- 保存用户配置的
servlet
的初始化参数- 验证是否有必须初始化的参数用户没有手动赋值
3.2 GenericServlet
类实现init()
方法
/**
* Called by the servlet container to indicate to a servlet that the
* servlet is being placed into service. See {@link Servlet#init}.
*
* <p>This implementation stores the {@link ServletConfig}
* object it receives from the servlet container for later use.
* When overriding this form of the method, call
* <code>super.init(config)</code>.
*
* @param config the <code>ServletConfig</code> object
* that contains configutation
* information for this servlet
*
* @exception ServletException if an exception occurs that
* interrupts the servlet's normal
* operation
*
* @see UnavailableException
*/
public void init(ServletConfig config) throws ServletException {
//这个是固定不变的
this.config = config;
//进行额外的初始化,见3.3
this.init();
}
/**
* 这是一个空方法
* 方便子类重写,在初始化过程中做额外的处理
*/
public void init() throws ServletException {
}
3.3 HttpServletBean
类实现重载的init()
方法
HttpServlet
并没有实现init()
方法,而是交由它的子类HttpServletBean
实现
//要求用户必须手动赋值的参数名集合
private final Set<String> requiredProperties = new HashSet<>(4);
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
//获取用户配置的servlet的初始化配置,并封装到ServletConfigPropertyValues中,见3.1
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//用户配置了当前servlet的初始化参数
if (!pvs.isEmpty()) {
try {
//将当前DispatcherServlet对象包装为BeanWrapperImpl对象
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册一个属性编辑器
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//初始化BeanWrapperImpl
initBeanWrapper(bw);
/**
* 将用户配置的servlet初始化参数填充到DispatcherServlet中,
* 这个过程不懂的话,建议去了解一下spring的属性填充
*/
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
//子类实现具体逻辑,见3.4
initServletBean();
}
/**
* 空方法,由子类实现
*/
protected void initServletBean() throws ServletException {
}
3.4 FrameworkServlet
类实现initServletBean()
方法
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
//初始化springmvc的上下文环境,见3.5
this.webApplicationContext = initWebApplicationContext();
//下面是一个空方法
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
3.5 初始化springmvc
的上下文环境
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
//获取用户配置的父上下文,见4.1
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
/**
* this.webApplicationContext肯定是不为null的
* 还记得我们自定义的MyWebApplicationInitializer类吗,当时我们构造DispacherServlet
* 的时候,把我们new的XmlWebApplicationContext对象传给它了
*/
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
//现在使用我们自己创建的XmlWebApplicationContext对象
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//实际上就是还没有调用上下文的refresh()方法
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);
}
//初始化web上下文,见3.5.1
configureAndRefreshWebApplicationContext(cwac);
}
}
}
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
//获取注册到ServletContext中的web上下文,见3.5.2
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
//创建一个默认的web上下文,见3.5.3
wac = createWebApplicationContext(rootContext);
}
//ContextRefreshListener未监听到ContextRefreshedEvent事件
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()方法为springmvc的9大组件设置初始值
* 该方法和ContextRefreshListener中调用的onRefresh()方法是同一个,见3.5.1.2
*/
onRefresh(wac);
}
}
//将springmvc上下文对象保存到ServletContext中
if (this.publishContext) {
// Publish the context as a servlet context attribute.
/**
* 前缀 org.springframework.web.servlet.FrameworkServlet.CONTEXT.
* 拼接上当前Servlet的名字(我指定的是ddispatcher)
* 所以attrName为org.springframework.web.servlet.FrameworkServlet.CONTEXT.ddispatcher
* 所以以后可以根据该名字从ServletContext中取出来
*/
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
总结该方法主要做的事情:
- 获取用户设置父上下文,并设置到
springmvc
上下文中- 设置一个用来监听上下文刷新事件
ContextRefreshedEvent
的监听器ContextRefreshListener
- 初始化
springmvc
上下文- 将
springmvc
上下文对象保存到ServletContext
中- 如果用户未手动创建
springmvc
上下文对象,那么它会自动创建一个web
上下文对象
3.5.1 初始化web
上下文
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
//设置web上下文的命名空间,见3.5.1.1
wac.setNamespace(getNamespace());
/**
* 添加spring的监听器ContextRefreshListener,见3.5.1.2
* SourceFilteringListener是一个监听器的包装,它可以过滤掉非同一个容器发布事件,见3.5.1.3
* 简单来说,就是父容器发布的事件只被由父容器的监听器监听到,子容器也是如此
*/
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
//获取web应用上下文的环境对象,3.5.1.4
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
//将ServletContext的配置注册到环境中,见3.5.1.5
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
//在执行refresh()方法前增强web上下文,见3.5.1.6
postProcessWebApplicationContext(wac);
/**
* 实例化ApplicationContextInitializer接口对象,
* 并调用接口的initialize()方法增强web上下文
*
* postProcessWebApplicationContext()和ApplicationContextInitializer接口
* 都是用来增强web上下文的
*/
applyInitializers(wac);
/**
* 调用上下文的refresh()方法,
* 这就回到了我们非常熟悉的spring环节了
*/
wac.refresh();
}
3.5.1.1 设置 web
上下文的命名空间
/**
* Return the namespace for this servlet, falling back to default scheme if
* no custom namespace was set: e.g. "test-servlet" for a servlet named "test".
*/
public String getNamespace() {
return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
命名空间的名字为当前
servlet
的名字加上"-servlet"
3.5.1.2 ContextRefreshListener
上下文刷新监听器
/**
* ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
* only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.
*/
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
/**
* ContextRefreshListener是FrameworkServlet的一个内部类,
* 在内部类中通过FrameworkServlet.this的到当前FrameworkServlet的对象
* 所以这句话的意思就是调用它外部类FrameworkServlet的onApplicationEvent方法
*/
FrameworkServlet.this.onApplicationEvent(event);
}
}
FrameworkServlet
的onApplicationEvent
方法
/**
* Callback that receives refresh events from this servlet's WebApplicationContext.
* <p>The default implementation calls {@link #onRefresh},
* triggering a refresh of this servlet's context-dependent state.
* @param event the incoming ApplicationContext event
*/
public void onApplicationEvent(ContextRefreshedEvent event) {
/**
* 表示已处理上下文刷新事件
* 这个属性在3.5中用到了,当这个监听器没有监听到ContextRefreshedEvent事件的时候
* DispatcherServlet就会自己手动调用onRefresh()方法为springmvc的9大组件设置初始值
* ContextRefreshedEvent事件是在spring流程finishRefresh()方法内发布的
*/
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
//监听到上下文刷新事件后具体做的事
onRefresh(event.getApplicationContext());
}
}
onRefresh()
方法被子类DispatcherServlet
重写了
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
//从方法名字也能看出来,这个方法用来初始化springmvc的9大组件
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
在
refresh()
方法调用结束之前,会发布一个ContextRefreshedEvent
事件ContextRefreshListener
这个监听器监听到上下文刷新事件后,初始化springmvc
上下文9
大组件
3.5.1.3 SourceFilteringListener
,包装监听器
下面是这个类的类图
public class SourceFilteringListener implements GenericApplicationListener, SmartApplicationListener {
private final Object source;
@Nullable
private GenericApplicationListener delegate;
/**
* Create a SourceFilteringListener for the given event source.
* @param source the event source that this listener filters for,
* only processing events from this source
* @param delegate the delegate listener to invoke with event
* from the specified source
*/
public SourceFilteringListener(Object source, ApplicationListener<?> delegate) {
//创建包装监听器时指定源对象
this.source = source;
//所有的监听器都被适配为GenericApplicationListener类型
this.delegate = (delegate instanceof GenericApplicationListener ?
(GenericApplicationListener) delegate : new GenericApplicationListenerAdapter(delegate));
}
//包装监听器的事件监听方法
@Override
public void onApplicationEvent(ApplicationEvent event) {
/**
* 比较事件的事件源对象和创建这个包装监听器时指定的source对象
* 不相同就什么也不干
*/
if (event.getSource() == this.source) {
onApplicationEventInternal(event);
}
}
/**
* Actually process the event, after having filtered according to the
* desired event source already.
* <p>The default implementation invokes the specified delegate, if any.
* @param event the event to process (matching the specified source)
*/
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);
}
}
这个监听器用来包装其他监听器,当事件的事件源对象和创建这个包装监听器时指定的
source
对象不相同时,就不会执行监听方法。即过滤掉其他容器发布的事件
3.5.1.4 获取web
应用上下文的环境对象
/**
* Return the {@code Environment} for this application context in configurable
* form, allowing for further customization.
* <p>If none specified, a default environment will be initialized via
* {@link #createEnvironment()}.
*/
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
//创建标准的web环境
this.environment = createEnvironment();
}
return this.environment;
}
createEnvironment()
方法在AbstractRefreshableWebApplicationContext
类中重写
/**
* Create and return a new {@link StandardServletEnvironment}. Subclasses may override
* in order to configure the environment or specialize the environment type returned.
*/
@Override
protected ConfigurableEnvironment createEnvironment() {
//标准的web环境
return new StandardServletEnvironment();
}
- 普通的
spring
应用程序创建的环境对象为StandardEnvironment
springmvc
应用程序创建的环境对象为StandardServletEnvironment
3.5.1.5 将ServletContext
的配置注册到环境中
@Override
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
/**
* getPropertySources()得到环境中真正存储配置集合
* 注册过程委派给了WebApplicationContextUtils.initServletPropertySources()方法完成
*/
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
注册ServletContext
的配置到环境中
/**
* Replace {@code Servlet}-based {@link StubPropertySource stub property sources} with
* actual instances populated with the given {@code servletContext} and
* {@code servletConfig} objects.
* <p>This method is idempotent with respect to the fact it may be called any number
* of times but will perform replacement of stub property sources with their
* corresponding actual property sources once and only once.
* @param sources the {@link MutablePropertySources} to initialize (must not
* be {@code null})
* @param servletContext the current {@link ServletContext} (ignored if {@code null}
* or if the {@link StandardServletEnvironment#SERVLET_CONTEXT_PROPERTY_SOURCE_NAME
* servlet context property source} has already been initialized)
* @param servletConfig the current {@link ServletConfig} (ignored if {@code null}
* or if the {@link StandardServletEnvironment#SERVLET_CONFIG_PROPERTY_SOURCE_NAME
* servlet config property source} has already been initialized)
* @see org.springframework.core.env.PropertySource.StubPropertySource
* @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
*/
public static void initServletPropertySources(MutablePropertySources sources,
@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
Assert.notNull(sources, "'propertySources' must not be null");
//SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams"
String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
if (servletContext != null && sources.get(name) instanceof StubPropertySource) {
sources.replace(name, new ServletContextPropertySource(name, servletContext));
}
//SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams"
name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
if (servletConfig != null && sources.get(name) instanceof StubPropertySource) {
sources.replace(name, new ServletConfigPropertySource(name, servletConfig));
}
}
spring
标准环境向环境中注册了两个配置systemEnvironment
和systemProperties
springmvc
标准环境向环境中额外注册了3
个配置servletContextInitParams
、servletConfigInitParams
、jndiProperties
3.5.1.6 postProcessWebApplicationContext()
方法,在执行refresh()
方法前增强web
上下文
/**
* Post-process the given WebApplicationContext before it is refreshed
* and activated as context for this servlet.
* <p>The default implementation is empty. {@code refresh()} will
* be called automatically after this method returns.
* <p>Note that this method is designed to allow subclasses to modify the application
* context, while {@link #initWebApplicationContext} is designed to allow
* end-users to modify the context through the use of
* {@link ApplicationContextInitializer ApplicationContextInitializers}.
* @param wac the configured WebApplicationContext (not refreshed yet)
* @see #createWebApplicationContext
* @see #initWebApplicationContext
* @see ConfigurableWebApplicationContext#refresh()
*/
protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
}
该方法默认为空
3.5.1.7 实例化ApplicationContextInitializer
接口对象,并调用接口的initialize()
方法增强web
上下文
/**
* Delegate the WebApplicationContext before it is refreshed to any
* {@link ApplicationContextInitializer} instances specified by the
* "contextInitializerClasses" servlet init-param.
* <p>See also {@link #postProcessWebApplicationContext}, which is designed to allow
* subclasses (as opposed to end-users) to modify the application context, and is
* called immediately before this method.
* @param wac the configured WebApplicationContext (not refreshed yet)
* @see #createWebApplicationContext
* @see #postProcessWebApplicationContext
* @see ConfigurableApplicationContext#refresh()
*/
protected void applyInitializers(ConfigurableApplicationContext wac) {
//获取用户在web.xml中配置的context-wide标签的内容
String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
/**
* INIT_PARAM_DELIMITERS = ",; \t\n"
* 根据分隔符分割字符串,并实例化
*/
if (globalClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
}
/**
* contextInitializerClasses是一个string类型属性
* 用户可是配置DispatcherServlet的时候指定初始值
*/
if (this.contextInitializerClasses != null) {
for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
//实例化
this.contextInitializers.add(loadInitializer(className, wac));
}
}
//根据Order接口或@Order注解排序
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
//执行接口的initialize方法增强web上下文
initializer.initialize(wac);
}
}
3.5.2 获取注册到ServletContext
中的web
上下文
/**
* Retrieve a {@code WebApplicationContext} from the {@code ServletContext}
* attribute with the {@link #setContextAttribute configured name}. The
* {@code WebApplicationContext} must have already been loaded and stored in the
* {@code ServletContext} before this servlet gets initialized (or invoked).
* <p>Subclasses may override this method to provide a different
* {@code WebApplicationContext} retrieval strategy.
* @return the WebApplicationContext for this servlet, or {@code null} if not found
* @see #getContextAttribute()
*/
@Nullable
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
/**
* 这个方法和我们获取父上下文不是同一个方法吗?
* 也就是说找不到子上下文对象的时候,就直接使用父上下文对象作为springmvc的上下文
*/
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
3.5.3 创建一个默认的web
上下文
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {@link #setContextClass custom context class}, if set.
* Delegates to #createWebApplicationContext(ApplicationContext).
* @param parent the parent WebApplicationContext to use, or {@code null} if none
* @return the WebApplicationContext for this servlet
* @see org.springframework.web.context.support.XmlWebApplicationContext
* @see #createWebApplicationContext(ApplicationContext)
*/
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
/**
* Instantiate the WebApplicationContext for this servlet, either a default
* {@link org.springframework.web.context.support.XmlWebApplicationContext}
* or a {@link #setContextClass custom context class}, if set.
* <p>This implementation expects custom contexts to implement the
* {@link org.springframework.web.context.ConfigurableWebApplicationContext}
* interface. Can be overridden in subclasses.
* <p>Do not forget to register this servlet instance as application listener on the
* created context (for triggering its {@link #onRefresh callback}, and to call
* {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
* before returning the context instance.
* @param parent the parent ApplicationContext to use, or {@code null} if none
* @return the WebApplicationContext for this servlet
* @see org.springframework.web.context.support.XmlWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
/**
* contextClass属性的默认值为XmlWebApplicationContext.class;
* 这是在类中固定写死的
*/
Class<?> contextClass = getContextClass();
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");
}
//实例化XmlWebApplicationContext
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//设置环境
wac.setEnvironment(getEnvironment());
//设置父容器
wac.setParent(parent);
//获取用户设置的配置文件位置(设置在DispatcherServlet中)
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//初始化刚刚创建的web上下文,见3.5.1
configureAndRefreshWebApplicationContext(wac);
return wac;
}
4 父子容器
4.1 获取用户配置的父上下文
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
spring
提供了一个工具类WebApplicationContextUtils
,通过工具类的getWebApplicationContext()
方法,可以得到用户在ServletContext
中配置的父上下文
/**
* Find the root {@code WebApplicationContext} for this web app, typically
* loaded via {@link org.springframework.web.context.ContextLoaderListener}.
* <p>Will rethrow an exception that happened on root context startup,
* to differentiate between a failed context startup and no context at all.
* @param sc the ServletContext to find the web application context for
* @return the root WebApplicationContext for this web app, or {@code null} if none
* @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
*/
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
//从ServletContext中获取父上下文对象
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
/**
* Find a custom {@code WebApplicationContext} for this web app.
* @param sc the ServletContext to find the web application context for
* @param attrName the name of the ServletContext attribute to look for
* @return the desired WebApplicationContext for this web app, or {@code null} if none
*/
@Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
//通过对应的名字取出父上下文对象
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (attr instanceof Exception) {
throw new IllegalStateException((Exception) attr);
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
是spring
定义的父上下文
在ServletContext
中的标识
//org.springframework.web.context.WebApplicationContext.ROOT
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
从上面的过程我们知道,如果我们想配置一个父子容器,那我们完全可以自己创建一个
web
上下文对象并初始化好(refresh()
方法),然后调用servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 自己创建的上下文对象);
方法将这个上下文对象保存到ServletContext
中。
4.2 配置一个父子容器
事实上
spring
为了简化配置,提供了一个ContextLoaderListener
监听器,它可以自己创建一个上下文,或者使用用户指定的上下文,并把它们作为父上下文。可以在MyWebApplicationInitializer
类这样配置
@Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherContext =
new AnnotationConfigWebApplicationContext();
dispatcherContext.register(DispatcherConfig.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
4.3 ContextLoaderListener
的原理
它是一个监听器,那么直接定位到contextInitialized(ServletContextEvent sce)
方法
/**
* Initialize the root web application context.
* 该方法会在web应用开始初始化的时候被回调
*/
@Override
public void contextInitialized(ServletContextEvent event) {
//调用父类方法
initWebApplicationContext(event.getServletContext());
}
父类initWebApplicationContext()
方法
/**
* Initialize Spring's web application context for the given servlet context,
* using the application context provided at construction time, or creating a new one
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
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!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
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.
//创建监听器对象的时候没有指定web上下文
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//上下文还没有调用refresh()方法
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);
}
//初始化上下文,见3.5.1,虽然两个方法并不是同一个方法,但内容差不多
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//父容器放到servlet上下文中,等待springmvc上下文初始化的时候取出来,见3.5.1
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.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
总结:
web
应用初始化之前,会回调所有监听器的contextInitialized()
方法ContextLoaderListener
创建了一个父上下文,并设置到Servlet
上下文中- 自动初始化父上下文(自动调用
refresh()
方法)
5 springmvc
默认的9
大组件的初始化策略
我们知道,springmvc
调用DispatcherServlet
类的onRefresh()
方法完成9
大组件的初始化
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
* 初始化springmvc的9大组件,默认策略,组件可被覆盖
*/
protected void initStrategies(ApplicationContext context) {
//文件上传解析器
initMultipartResolver(context);
//区域解析器
initLocaleResolver(context);
//主题解析器
initThemeResolver(context);
//处理器映射器
initHandlerMappings(context);
//处理器适配器
initHandlerAdapters(context);
//处理器异常解析器
initHandlerExceptionResolvers(context);
//视图名转换器
initRequestToViewNameTranslator(context);
//视图解析器
initViewResolvers(context);
//FlashMapManager
initFlashMapManager(context);
}
5.1 MultipartResolver
文件上传解析器
//规定的文件上传解析器的名字
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
/**
* Initialize the MultipartResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* no multipart handling is provided.
*/
private void initMultipartResolver(ApplicationContext context) {
//从容器中获取名字为multipartResolver的bean,就是文件上传解析器
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.multipartResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
//用户没有配置文件上传解析器,那么系统的就为null
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
}
}
}
springmvc
并没有配置默认的MultipartResolver
,必须用户手动配置- 用户手动配置的文件上传解析器的名字必须为
multipartResolver
5.2 LocaleResolver
区域解析器
//规定的区域解析器的名字
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
/**
* Initialize the LocaleResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to AcceptHeaderLocaleResolver.
*/
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.localeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
//使用springmvc默认的LocaleResolver,见5.10
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
}
}
}
- 如果用户没有配置
LocaleResolver
,就使用springmvc
默认的AcceptHeaderLocaleResolver
- 用户手动配置的区域解析器的名字必须为
localeResolver
5.3 ThemeResolver
主题解析器
//规定的主题解析器的名字
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
/**
* Initialize the ThemeResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to a FixedThemeResolver.
*/
private void initThemeResolver(ApplicationContext context) {
try {
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.themeResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
//使用springmvc默认的ThemeResolver,见5.10
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No ThemeResolver '" + THEME_RESOLVER_BEAN_NAME +
"': using default [" + this.themeResolver.getClass().getSimpleName() + "]");
}
}
}
- 如果用户没有配置
ThemeResolver
,就使用springmvc
默认的FixedThemeResolver
- 用户手动配置的主题解析器的名字必须为
themeResolver
5.4 HandlerMapping
处理器映射器
//这个属性默认为true,表明允许用户全面接管HandlerMapping
private boolean detectAllHandlerMappings = true;
//规定的处理器映射器的名字
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
//允许用户全面接管HandlerMapping
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
//获取容器中所有类型为HandlerMapping的bean
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
//排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
//只允许使用名字为handlerMapping的处理器映射器
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
//使用系统默认的HandlerMapping配置策略,见5.10
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
- 如果用户没有配置
HandlerMapping
,就使用springmvc
默认的BeanNameUrlHandlerMapping
、SimpleControllerHandlerAdapter
、RequestMappingHandlerMapping
、HandlerFunctionAdapter
- 用户手动配置的处理器映射器的名字随意,因为是根据类型查找的。
5.5 HandlerAdapter
处理器适配器
//这个属性默认为true,表明允许用户全面接管HandlerAdapter
private boolean detectAllHandlerAdapters = true;
//规定的处理器适配器的名字
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";
/**
* Initialize the HandlerAdapters used by this class.
* <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
* we default to SimpleControllerHandlerAdapter.
*/
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
//允许用户全面接管HandlerAdapter
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
//获取容器中所有类型为HandlerAdapter的bean
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
//排序
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
//只允许使用名字为handlerAdapter的处理器适配器
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
//使用系统默认的HandlerAdapter配置策略,见5.10
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
- 如果用户没有配置
HandlerAdapter
,就使用springmvc
默认的HttpRequestHandlerAdapter
、SimpleControllerHandlerAdapter
、RequestMappingHandlerAdapter
、HandlerFunctionAdapter
- 用户手动配置的
HandlerAdapter
的名字随意,因为是根据类型查找的。
5.6 HandlerExceptionResolver
处理器异常解析器
//这个属性默认为true,表明允许用户全面接管HandlerExceptionResolver
private boolean detectAllHandlerExceptionResolvers = true;
//规定的处理器异常解析器的名字
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";
/**
* Initialize the HandlerExceptionResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to no exception resolver.
*/
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
//允许用户全面接管HandlerExceptionResolver
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
//只允许使用名字为handlerExceptionResolver的处理器异常解析器
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
//使用系统默认的HandlerExceptionResolver配置策略,见5.10
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
- 如果用户没有配置
HandlerExceptionResolver
,就使用springmvc
默认的ExceptionHandlerExceptionResolver
、ResponseStatusExceptionResolver
、DefaultHandlerExceptionResolver
- 用户手动配置的
HandlerExceptionResolver
的名字随意,因为是根据类型查找的。
5.7 RequestToViewNameTranslator
视图名转换器
//规定的视图名转换器的名字
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
/**
* Initialize the RequestToViewNameTranslator used by this servlet instance.
* <p>If no implementation is configured then we default to DefaultRequestToViewNameTranslator.
*/
private void initRequestToViewNameTranslator(ApplicationContext context) {
try {
//从容器中获取
this.viewNameTranslator =
context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName());
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.viewNameTranslator);
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
//使用springmvc默认的RequestToViewNameTranslator,见5.10
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
if (logger.isTraceEnabled()) {
logger.trace("No RequestToViewNameTranslator '" + REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME +
"': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]");
}
}
}
- 如果用户没有配置
RequestToViewNameTranslator
,就使用springmvc
默认的DefaultRequestToViewNameTranslator
- 用户手动配置的
RequestToViewNameTranslator
的名字必须为viewNameTranslator
5.8 ViewResolver
视图解析器
//这个属性默认为true,表明允许用户全面接管ViewResolver
private boolean detectAllViewResolvers = true;
//规定的视图解析器的名字
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
/**
* Initialize the ViewResolvers used by this class.
* <p>If no ViewResolver beans are defined in the BeanFactory for this
* namespace, we default to InternalResourceViewResolver.
*/
private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null;
//允许用户全面接管ViewResolver
if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<>(matchingBeans.values());
// We keep ViewResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
}
//只允许使用名字为viewResolver的视图解析器
else {
try {
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default ViewResolver later.
}
}
// Ensure we have at least one ViewResolver, by registering
// a default ViewResolver if no other resolvers are found.
if (this.viewResolvers == null) {
//使用系统默认的ViewResolver配置策略,见5.10
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
- 如果用户没有配置
ViewResolver
,就使用springmvc
默认的InternalResourceViewResolver
- 用户手动配置的
ViewResolver
的名字随意,因为是根据类型查找的。
5.9 FlashMapManager
//规定的FlashMapManager的名字
public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";
/**
* Initialize the {@link FlashMapManager} used by this servlet instance.
* <p>If no implementation is configured then we default to
* {@code org.springframework.web.servlet.support.DefaultFlashMapManager}.
*/
private void initFlashMapManager(ApplicationContext context) {
try {
this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.flashMapManager);
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
//使用springmvc默认的FlashMapManager,见5.10
this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
if (logger.isTraceEnabled()) {
logger.trace("No FlashMapManager '" + FLASH_MAP_MANAGER_BEAN_NAME +
"': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");
}
}
}
- 如果用户没有配置
FlashMapManager
,就使用springmvc
默认的SessionFlashMapManager
- 用户手动配置的
FlashMapManager
的名字必须为flashMapManager
5.10 springmvc
默认的组件配置策略
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
//保存了DispatcherServlet.properties文件的内容
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
//加载DispatcherServlet.properties文件的内容
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
/**
* Create a List of default strategy objects for the given strategy interface.
* <p>The default implementation uses the "DispatcherServlet.properties" file (in the same
* package as the DispatcherServlet class) to determine the class names. It instantiates
* the strategy objects through the context's BeanFactory.
* @param context the current WebApplicationContext
* @param strategyInterface the strategy interface
* @return the List of corresponding strategy objects
*/
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
//组件接口的完全限定名
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
//配置了多个,以,分割
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
//反射实例化对象
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
那接下来我们再来看一下这个DispatcherServlet.properties
文件的内容
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
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.function.support.RouterFunctionMapping
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.function.support.HandlerFunctionAdapter
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
springmvc
默认的组件配置就是上面文件描述的这些了,一共8
种(不包含文件上传解析器)
LocaleResolver
:默认的组件类型为AcceptHeaderLocaleResolver
ThemeResolver
:默认的组件类型为FixedThemeResolver
HandlerMapping
:默认配置了4
个,组件类型分别为为BeanNameUrlHandlerMapping
、SimpleControllerHandlerAdapter
、RequestMappingHandlerMapping
、HandlerFunctionAdapter
HandlerAdapter
:默认配置了4
个,组件类型分别为为HttpRequestHandlerAdapter
、SimpleControllerHandlerAdapter
、RequestMappingHandlerAdapter
、HandlerFunctionAdapter
。很明显是和HandlerMapping
一一对应的HandlerExceptionResolver
:默认配置了3
个,组件类型分别为为ExceptionHandlerExceptionResolver
、ResponseStatusExceptionResolver
、DefaultHandlerExceptionResolver
RequestToViewNameTranslator
:默认的组件类型为DefaultRequestToViewNameTranslator
ViewResolver
:默认的组件类型为InternalResourceViewResolver
FlashMapManager
:默认的组件类型为SessionFlashMapManager
5.11 总结
MultipartResolver
、LocaleResolver
、ThemeResolver
、RequestToViewNameTranslator
、FlashMapManager
这5
个组件必须按照规定的名字注册到容器中,才能被springmvc
使用HandlerMapping
、HandlerAdapter
、HandlerExceptionResolver
、ViewResolver
则不要求特定的名字,且它们可以同时在容器中配置多个,同时生效