@[toc]
spring的web应用的上下文并不是在容器启动的时候就进行初始化的,而是在第一次请求的时候进行初始化的。
1.从Servlet
规范到Spring
1.1 Servlet
规范部分说明
spring的web应用(不包含web-flux)是基于Servlet
规范进行的搭建的,这里需要先讲一下Servlet
规范的部分内容,因为spring的web上下文的初始化也是要从这里开始的。
Servlet
是按照一个严格定义的生命周期被管理,该生命周期规定了Servlet
如何被加载、实例化、初始化、处理客户端请求,以及何时结束服务。该声明周期可以通过 javax.servlet.Servlet
接口中的init
、service
和 destroy
这些 API 来表示,所有 Servlet
必须直接或间接的实现 GenericServlet
或HttpServlet
抽象类。
servlet
对象实例化后,容器必须初始化 servlet
之后才能处理客户端的请求。初始化的目的是以便 Servlet
能读取持久化配置数据,初始化一些代价高的资源(比如JDBC™ API
连接),或者执行一些一次性的动作。容器通过调用 Servlet
实例的 init
方法完成初始化,init
方法定义在Servlet
接口中,并且提供一个唯一的 ServletConfig
接口实现的对象作为参数,该对象每个 Servlet 实例一个。
1.2 分析初始化的入口
从上面的规范说明中知道了,spring会实现GenericServlet
抽象类的init
方法来初始化相关的信息,
通过查看init
方法的实现类,发现在spring的所有的这个方法的实现中只有HttpServletBean
是符合需求的。因此我们进入到这个方法。
2. 初始化上下文的入口HttpServletBean
这里直接进入到HttpServletBean
实现的init
方法进行分析。
public final void init() throws ServletException {
// Set bean properties from init parameters.
//获取ServletConfig,从servlet中获取对应的初始化参数,ServletConfig是servlet规范中的接口类
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//如果初始化参数不为空,则需要将
if (!pvs.isEmpty()) {
try {
//获取HttpServletBean的BeanWrapper
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//讲ServletContext转化为一个ResourceLoader
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//创建ResourceEditor并保存到HttpServletBean中
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//这里是空方法,可以子类实现
initBeanWrapper(bw);
//将ServletConfig对应的属性保存到HttpServletBean
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
//进行初始化,这个是一个交给子类实现的方法,可以自定义初始化的逻辑
initServletBean();
}
可以看到实现的逻辑也是比较简单的,就是从ServletConfig
获取servlet容器中的相关的初始化的参数,如果属性不为空的时候,可以定义自己的属性处理逻辑,这里会调用。处理完容器中相关的初始化参数之后就是进行初始化了。这个初始化的方法initServletBean
是交给子类去自由实现初始化逻辑的。
这里补充说明ServletConfig
跟ServletContext
这两个类都是servlet
规范中的接口类。这里进行简单的说明一下
类 | 说明 |
---|---|
ServletConfig | ServletConfig是每个 Servlet 实例一个,配置对象允许 Servlet 访问由 Web 应用配置信息提供的键-值对的初始化参数 |
ServletContext | ServletContext 接口定义了 servlet 运行在的 Web 应用的视图,servlet 可以使用 ServletContext 对象记录事件,获取 URL 引用的资源,存取当前上下文的其他 servlet 可以访问的属性。默认的 ServletContext 是非分布式的且仅存在于一个 JVM 中。 |
3. 初始化web应用上下文的FrameworkServlet
在上面提到的initServletBean
方法,其实现在spring中只有一个类实现了那就是FrameworkServlet
。这个类是spring对spring应用上下文的集成类。作用很多:
- 为每个servlet应用管理一个
WebApplicationContext
对象。 - 在请求处理时发布事件,不管是否成功处理了请求
这个类也提供了很多扩展,其子类必须实现这个类的doService
方法。同时也可以重载initFrameworkServlet
来自定义初始化。
现在看看FrameworkServlet
如何进行应用的初始化的,直接进入到initServletBean
进行查看。
protected final void initServletBean() throws ServletException {
//获取ServletContext,打印servlet应用的名称
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
//获取当前系统时间,用于计算初始化的时长
long startTime = System.currentTimeMillis();
try {
//初始化web上下文WebApplicationContext
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");
}
}
可以看到这里只是简单的几个方法的调用,就分别完成了spring的web上下文初始化,以及其他的初始化,其中后面的初始化时可以自己定义逻辑的,默认是空的。这里主要就看上下文的初始化。
protected WebApplicationContext initWebApplicationContext() {
//如果用户指定了WebApplicationContext情况下,从ServletContext获取WebApplicationContext,这里获取的属性名是WebApplicationContext.class.getName() + ".ROOT"
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//如果webApplicationContext不是null,说明已经初始化过了,在spring容器初始化的时候,会设置这个值得,因此这里判断是true
if (this.webApplicationContext != null) {
//设置已经存在的上下文
wac = this.webApplicationContext;
//如果是ConfigurableWebApplicationContext类型的,需要先设置WebApplicationContext为根上下文
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//检查当前上下文是否是活跃的,既可以用的,如果不是的则需要刷新
if (!cwac.isActive()) {
//如果还没有设置根上下文则进行设置
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
//配置并刷新web上下文
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//如果wac是null,则书名web上下文是null,则在servlet中寻找是否存在web上下文,这里寻找的是属性名为FrameworkServlet.class.getName() + ".CONTEXT."+当前servlet的名称的上下文
if (wac == null) {
wac = findWebApplicationContext();
}
//如果servlet中也不存在上下文,则创建一个
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//检查onRefresh是否已经调用了
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
//模板方法,可以覆盖该方法以添加特定于servlet的刷新工作。成功刷新上下文后调用
onRefresh(wac);
}
}
//我们应该将上下文作为ServletContext属性发布吗,默认为true
if (this.publishContext) {
//获取要设置的属性名 FrameworkServlet.class.getName() + ".CONTEXT."+当前servlet的名称
String attrName = getServletContextAttributeName();
//讲当前上下文设置到servlet上下文中
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
这里在逻辑主要分文两点,寻找可以用的web应用上下文,然后进行时间的发布跟其他刷新工作。这里需要说明几点的是:
- 实例化跟初始化是有区别的,在这里方法这里是初始化而不是实例化
- 在正常情况下进入到这个方法的时候,是在spring的容器已经初始化完毕以后了,因为这个时候整个服务已经起来了,容器里面会包含对应的web应用上下文,因此上面方法中的
webApplicationContext
不会为null - 对应的web的服务也是活跃状态,因此上面方法中的
isActive
的判断是true。
对于其中的第二点,这里说明一下webApplicationContext
是什么时候被设置进来的,因为FrameworkServlet
实现了ApplicationContextAware
接口,因此在FrameworkServlet
实例化之后初始化之前会调用实现的setApplicationContext
方法
public void setApplicationContext(ApplicationContext applicationContext) {
if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
this.webApplicationContext = (WebApplicationContext) applicationContext;
this.webApplicationContextInjected = true;
}
}
这里关于ApplicationContextAware
可以看看对应的前面的spring的生命周期的文章Spring源码----Spring的Bean生命周期流程图及代码解释
4. 初始化spring相关web相关类的DispatcherServlet
4.1 初始化servlet的常用策略
我们注意到在FrameworkServlet
中的initWebApplicationContext
方法中有这么的一个步骤,在找到了web应用上下文之后,会有一个检查onRefresh
方法是否已经调用了的步骤,如果没有调用就会调用对应的onRefresh
方法,如果调用了就不用处理,那么还有哪里会调用呢。现在要分析就跟这个有关。
onRefresh
方法在FrameworkServlet
中是空逻辑的,其主要逻辑在DispatcherServlet
这个类中进行的。接下来就进入到里面
@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) {
//初始化解析文件相关的MultipartResolver,寻找对应的bean,然后保存
initMultipartResolver(context);
//初始化区域解析用的LocaleResolver,寻找对应的bean,然后保存
initLocaleResolver(context);
//初始化解析主题用的ThemeResolver,寻找对应的bean,然后保存
initThemeResolver(context);
//初始化handlerMapping,逻辑就是找到容器中所有的HandlerMapping类型的bean然后放到一个集合中
initHandlerMappings(context);
//初始化HandlerAdapter,逻辑就是找到容器中所有的HandlerAdapter类型的bean然后放到一个集合中
initHandlerAdapters(context);
//初始化HandlerExceptionResolver,逻辑跟上面的一样寻找HandlerExceptionResolver类型的bean,然后保存
initHandlerExceptionResolvers(context);
//初始化请求视图(url)转化为本地试图(url)的RequestToViewNameTranslator,寻找对应的bean,然后保存
initRequestToViewNameTranslator(context);
//初始化ViewResolver试图解析器,逻辑跟上面的一样寻找ViewResolver类型的bean,然后保存
initViewResolvers(context);
//初始化FlashMapManager,用来管理FlashMap(保存两个视图url之间的关系,在用direct的时候会用到)
initFlashMapManager(context);
}
是不是感觉很多熟悉的东西出现了,没错这就是整个springmvc会用到的东西会在这里进行装载保存,在后面进行处理的时候用到。
到这里spring的web应用会用的东西在这里就已经初始化完成了。
网友评论