Servlet
规范到SpringServlet
规范部分说明 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 实例一个。
从上面的规范说明中知道了,spring会实现GenericServlet
抽象类的init
方法来初始化相关的信息,
通过查看init
方法的实现类,发现在spring的所有的这个方法的实现中只有HttpServletBean
是符合需求的。因此我们进入到这个方法。
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 中。 |
FrameworkServlet
在上面提到的initServletBean
方法,其实现在spring中只有一个类实现了那就是FrameworkServlet
。这个类是spring对spring应用上下文的集成类。作用很多:
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应用上下文,然后进行时间的发布跟其他刷新工作。这里需要说明几点的是:
webApplicationContext
不会为nullisActive
的判断是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生命周期流程图及代码解释
DispatcherServlet
我们注意到在FrameworkServlet
中的initWebApplicationContext
方法中有这么的一个步骤,在找到了web应用上下文之后,会有一个检查onRefresh
方法是否已经调用了的步骤,如果没有调用就会调用对应的onRefresh
方法,如果调用了就不用处理,那么还有哪里会调用呢。现在要分析就跟这个有关。
onRefresh
方法在FrameworkServlet
中是空逻辑的,其主要逻辑在DispatcherServlet
这个类中进行的。接下来就进入到里面
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* 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应用会用的东西在这里就已经初始化完成了。