前言

Spring应该是后端Java开发者使用最多框架,感觉大家对这个框架也很了解比如IOCAOP这两个特性是最为熟悉,而且很多大佬都知道Spring是怎么初始化Spring的Bean从refresh()方法开始讲然后滔滔不绝能说好久。不得不说Spring这种低侵入的轻量化的框架受到大多开发者追捧。而且它DI做的真的真的很不错,比EJB好要用很多。

不过说了这么多,我上面那些都不是我这篇要写博客的重点。我打算说下关于Spring容器是如何在Web容器启动(tomcat/jetty)SpringMVC是怎么实现Servlet3.0的规范。我们平时都只和业务中的controllerservicedao打交道很多,但是这些是如何被Spring被启动并注入到Spring容器这个过程是很有意思的。不过现在都是使用SpringBoot。让我们今天考考古,挖挖坟。

这篇主要讲怎么通过传统的SpringMVC去创建一个webApplicationContext对象。

在聊SpringMVC之前我先说说Servlet,可以说Servlet是JavaWeb程序比较核心的东西,在没有出现SpringMVC之前都是使用Servlet和JSP去实现接口和页面。在Servlet之前使用的是CGI接口,这种是不限制语言。这种我们就不说了,毕竟这个现在都用不上。下面我们说说什么是Servlet。

什么是Servlet

Servlet(Server Applet),全称Java Servlet。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况Servlet只用来扩展基于HTTP协议的Web服务器。Tomcat/Jetty 这种Web应用服务器软件,本质是处理Servlet。

当servlet被部署在应用服务器中(应用服务器中用于管理Java组件的部分被抽象成为容器)以后,由容器控制servlet的生命周期。除非特殊指定,否则在容器启动的时候,servlet是不会被加载的,servlet只会在第一次请求的时候被加载和实例化。servlet一旦被加载,一般不会从容器中删除,直至应用服务器关闭或重新启动。但当容器做存储器回收动作时,servlet有可能被删除。也正是因为这个原因,第一次访问servlet所用的时间要大大多于以后访问所用的时间。

Servlet周期

一个Servlet对象服务周期

1.实例化(使用构造方法创建对象);2.初始化 执行init()方法;3.服务 执行service()方法;4.销毁 执行destroy()方法。

调用用过程如下

构造方法在创建servlet对象时候使用,如果有构造方法调用构造方法,否则调用默认构造方法。

init()方法当Servlet第一次被创建对象时执行该方法,该方法在整个生命周期中只执行一次。

service()对客户端响应的方法,该方法会被执行多次,每次请求该servlet都会执行该方法。

destroy()当Servlet被销毁时执行该方法。

下面这张图清楚展示了Servlet在web服务器上的应用。

在servlet中每个请求创建的是线程,而不是进程,提高性能。使用java跨平台语言,实现了可移植(一次编写处处异常)。使用JVM管理内存,不会担心内存管理,溢出问题(不过代码写的烂依旧可以溢出)。不过我们也看出来servlet不是线程安全的在单个状态变量在多个线程下活跃性问题。

Servlet处理请求的简单过程

Servlet 是用 Java 编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态 Web 内容。

  1. 请求到达 server 端,server 根据 url 映射到相应的 Servlet
  2. 判断 Servlet 实例是否存在,不存在则加载和实例化 Servlet 并调用 init 方法
  3. Server 分别创建 Request 和 Response 对象,调用 Servlet 实例的 service 方法(service 方法 内部会根据 http 请求方法类型调用相应的 doXXX 方法)
  4. doXXX 方法内为业务逻辑实现,从 Request 对象获取请求参数,处理完毕之后将结果通过 response 对象返回给调用方
  5. 当 Server 不再需要 Servlet 时(一般当 Server 关闭时),Server 调用 Servlet 的 destroy() 方 法。

使用Servlet注意的点

load on startup

当值为 0 或者大于 0 时,表示容器在应用启动时就加载这个 servlet; 当是一个负数时或者没有指定时,则指示容器在该 servlet 被选择时才加载; 正数的值越小,启动该 servlet 的优先级越高;

single thread model

每次访问 servlet,新建 servlet 实体对象,但并不能保证线程安全,同时 tomcat 会限制 servlet 的实例数目 最佳实践:不要使用该模型,servlet 中不要有全局变量。

SpringMVC的引入

servlet解决一部分CGI的问题,但是servlet还不是很完美。特别使servlet处理大量Html文件,同时在程序复杂性提高时候,servlet不能处理解决高耦合的问题。不过随着后面Spring框架出现结合MVC三层架构(中间也有过Strust),出现SpringMVC架构。让开发者更好更快捷的开发。

SpringMVC和Servlet的结合

Spring web MVC框架提供了MVC(模型 - 视图 - 控制器)架构和用于开发灵活和松散耦合的Web应用程序的组件。MVC模式导致应用程序的不同方面(输入逻辑,业务逻辑和UI逻辑)分离,同时提供这些元素之间的松散耦合。

  • 模型(Model):封装了应用程序的数据,通常由POJO类组成
  • 视图(View):负责渲染模型数据,一般来说它生成客户端浏览器可以解释HTML输出
  • 控制器(Controller):负责处理用户请求并构建适当的模型,并将其传递给视图进行渲染

从上面我们看出来真正实现解耦使FrontController这个对象,也就是DispatcerServlert。好多关于Spring容器启动初始化等等都是在这类操作的。同时他也对请求进行分发,分发给各个controller。

DispatcherServlet处理HTTP请求的工作流程

  1. 接受HTTP请求后,DispatcherServlet处理HttpRequest。
  2. 会查询HandlerMapping以调用相应的Controller(根据请求的url)
  3. Controller接受请求并根据请求的类型Get/Post调用相应的服务方法,服务方法进行相应的业务处理,并设置模型数据,最后将视图名称返回给DispatcherServlet
  4. DispatcherServlet根据返回的视图名称从ViewResolver获取对应的视图
  5. DispatcherServlet将模型数据传递到最终的视图,并将视图处理成HttpResponse返回给浏览器。

DispatcherServlet启动

说明白DispatcherServlet的启动我们还要说个文件web.xml。这个估计熟悉SpringMVC都应该配置过这个文件,那这个文件是什么作用,为什么会有这个文件,好像说明白的人不多,我参考了一些文档希望能给你不错的解答。

web.xml的作用和由来

首先web.xml是来自Java Servlet的标准,这个标准从Servlet2.2标准就有了。

这个里面对应每个标准和对用实现的Java服务实现的版本。对于xml我想熟悉编程的朋友应该知道xml定义,根据xml定义我们也能判断出servlet的协议版本。

1
2
3
4
5
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">

上面这个web.xml使用的是servlet2.4版本。对应Tomcat版本5.5版本以上都是支持的。

1
2
3
4
5
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">

上面这个说是我从最新Towcat10上的web.xml上copy下的。这个都已经是Servlet的5.0版本,而且这个协议已经和Sun公司没关系了。

Java服务器使用web.xml

Java服务器在使用会使用web.xml去额外去加载一些类或者对象。你可以去下载一个tomcat服务器,打开conf文件夹,这个Tomcat启动的默认web.xml。web.xml主要用于配置Filter、Listener、Servlet,欢迎页,错误页等等。

Web容器的加载顺序ServletContext -> context-param -> listener -> filter -> servlet。并且这些元素可以配置在文件中的任意位置,不会因为filter在web.xml文件中写在listener前面就先加载filter。

加载过程顺序如下

  • 启动一个web项目,web容器会读取它的配置文件web.xml,读取<listener><context-param>两个结点。
  • 创建一个ServletContext(Servlet上下文),这个web项目的所有部分都将共享这个上下文
  • 容器将<context-param>转换为键值对,并交给ServletContext
  • 容器创建<listener>中的类实例,创建监听器
  • 容器创建对应的filter和Servlet启动对应Spring的容器 。

下面我们在看这段配置就可以很容易理解。这段配置是关于DispatcherServlet。

1
2
3
4
5
6
7
8
9
 <servlet>
<servlet-name>treasureWeb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/applicationContext-main.xml,classpath:config/applicationContext-converter.xml,classpath:config/applicationContext-async.xml,classpath:config/applicationContext-interceptor.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

org.springframework.web.servlet.DispatcherServlet这个类被设置第一个启动。

DispatcherServlert关于容器启动部分的源码

首先我们看下DispatcherServlert这个类的继承关系。

从这个继承关系我们就知道创建DispatherServlet对象大概要从那个对象开始创建。因为面向对象创建子类都会先调用父类的构造函数(这个面向对象都有讲我就不多解释)。

这里我们就从DispatchServlet的构造函数说起。NO BB SHOW MY CODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//加载DispatcherServlet.properties内容,里面包含了很多Dispatcher的策略方法实现。这里不展开
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}

/**
* Create a new {@code DispatcherServlet} that will create its own internal web
* application context based on defaults and values provided through servlet
* init-params. Typically used in Servlet 2.5 or earlier environments, where the only
* option for servlet registration is through {@code web.xml} which requires the use
* of a no-arg constructor.
* <p>Calling {@link #setContextConfigLocation} (init-param 'contextConfigLocation')
* will dictate which XML files will be loaded by the
* {@linkplain #DEFAULT_CONTEXT_CLASS default XmlWebApplicationContext}
* <p>Calling {@link #setContextClass} (init-param 'contextClass') overrides the
* default {@code XmlWebApplicationContext} and allows for specifying an alternative class,
* such as {@code AnnotationConfigWebApplicationContext}.
* <p>Calling {@link #setContextInitializerClasses} (init-param 'contextInitializerClasses')
* indicates which {@code ApplicationContextInitializer} classes should be used to
* further configure the internal application context prior to refresh().
* @see #DispatcherServlet(WebApplicationContext)
*/
public DispatcherServlet() { //上面这些是解释,核心一句话 @see #DispatcherServlet(WebApplicationContext)
super();
setDispatchOptionsRequest(true);
}

/**
* Create a new {@code DispatcherServlet} with the given web application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based registration
* of servlets is possible through the {@link ServletContext#addServlet} API.
* <p>Using this constructor indicates that the following properties / init-params
* will be ignored:
* <ul>
* <li>{@link #setContextClass(Class)} / 'contextClass'</li>
* <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li>
* <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li>
* <li>{@link #setNamespace(String)} / 'namespace'</li>
* </ul>
* <p>The given web application context may or may not yet be {@linkplain
* ConfigurableApplicationContext#refresh() refreshed}. If it has <strong>not</strong>
* already been refreshed (the recommended approach), then the following will occur:
* <ul>
* <li>If the given context does not already have a {@linkplain
* ConfigurableApplicationContext#setParent parent}, the root application context
* will be set as the parent.</li>
* <li>If the given context has not already been assigned an {@linkplain
* ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #postProcessWebApplicationContext} will be called</li>
* <li>Any {@code ApplicationContextInitializer}s specified through the
* "contextInitializerClasses" init-param or through the {@link
* #setContextInitializers} property will be applied.</li>
* <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called if the
* context implements {@link ConfigurableApplicationContext}</li>
* </ul>
* If the context has already been refreshed, none of the above will occur, under the
* assumption that the user has performed these actions (or not) per their specific
* needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* @param webApplicationContext the context to use
* @see #initWebApplicationContext
* @see #configureAndRefreshWebApplicationContext
* @see org.springframework.web.WebApplicationInitializer
*/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
//这个Context应该就是Spring相关的Conext,下面会有关于WebApplicationContext的源码
//他是调用父类的构造函数去实现的这个地方。我们就一起看看它的父类
super(webApplicationContext);
setDispatchOptionsRequest(true);
}

WebApplicationContext接口的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_GLOBAL_SESSION = "globalSession";
String SCOPE_APPLICATION = "application";
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

ServletContext getServletContext();
}
//我们能看出来这个就是Spring的ApplicationContext,只不过是web版本。

FrameworkServlet构造函数源码,这个地方我去找了configureAndRefreshWebApplicationContext()方法。好在这个方法也是在FrameworkServlet里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Create a new {@code FrameworkServlet} with the given web application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based registration
* of servlets is possible through the {@link ServletContext#addServlet} API.
* <p>Using this constructor indicates that the following properties / init-params
* will be ignored:
* <ul>
* <li>{@link #setContextClass(Class)} / 'contextClass'</li>
* <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li>
* <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li>
* <li>{@link #setNamespace(String)} / 'namespace'</li>
* </ul>
* <p>The given web application context may or may not yet be {@linkplain
* ConfigurableApplicationContext#refresh() refreshed}. If it (a) is an implementation
* of {@link ConfigurableWebApplicationContext} and (b) has <strong>not</strong>
* already been refreshed (the recommended approach), then the following will occur:
* <ul>
* <li>If the given context does not already have a {@linkplain
* ConfigurableApplicationContext#setParent parent}, the root application context
* will be set as the parent.</li>
* <li>If the given context has not already been assigned an {@linkplain
* ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #postProcessWebApplicationContext} will be called</li>
* <li>Any {@link ApplicationContextInitializer}s specified through the
* "contextInitializerClasses" init-param or through the {@link
* #setContextInitializers} property will be applied.</li>
* <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called</li>
* </ul>
* If the context has already been refreshed or does not implement
* {@code ConfigurableWebApplicationContext}, none of the above will occur under the
* assumption that the user has performed these actions (or not) per his or her
* specific needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* @param webApplicationContext the context to use
* @see #initWebApplicationContext
* @see #configureAndRefreshWebApplicationContext
* @see org.springframework.web.WebApplicationInitializer
*/
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}

FrameworkServlet类的createWebApplicationContext()方法就是在没有WebApplicationContext对象时创建。这个地方如果没有指定configureAndRefreshWebApplicationContext()方法源码,启动容器。因为这里有refresh()方法。它是被initWebApplicationContext()方法调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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());
wac.setNamespace(getNamespace());
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
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}

postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}

FrameworkServlet类的createWebApplicationContext()方法就是在没有WebApplicationContext对象时创建。这个地方如果没有指定initWebApplicationContext()这个方法我们能看出来,如果这个地方有webApplicationContext就去使用,如果没有就去查找,如果也没查到,就自己通过反射去创建createWebApplicationContext这个方法。返回一个WebApplicationContext对象对于Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* 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() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;

if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
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
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}

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.
onRefresh(wac);
}

if (this.publishContext) {
// Publish the context as a servlet context attribute.
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;
}

FrameworkServlet类的createWebApplicationContext()方法就是在没有WebApplicationContext对象时创建。这个地方如果没有指定默认的就是XmlWebApplicationContext.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
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 + "]");
}
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");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
//这个地方还会回调它原来的方法,递归
configureAndRefreshWebApplicationContext(wac);

return wac;
}

FrameworkServlet类的initServletBean()方法是HttpServletBean方法的重写,主要就是对webApplicationContext成员对象赋值,并创建webApplicationContext对象。这个方法是HttpServletBean类的抽象方法,FrameworkServlet实现。initServletBean()方法类似一个钩子,什么的实现类实现了方法,那么就产什么什么样的对象。 initServletBean()方法在HttpServletBean类被init()方法调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/**
* 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 FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();

try {
this.webApplicationContext = initWebApplicationContext();
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");
}
}

HttpServletBean的init()方法是实现了GenericServlet类的init()方法。这里主要是初始化HttpServletBean对象,同时将配置文里的Bean的属性都拿出来。从这个GenericServlet类你会发现,你已经不在Spring相关的类而是Servlet相关的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 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 {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}

// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}

// Let subclasses do whatever initialization they like.
initServletBean();

if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}


/**
* Subclasses may override this to perform custom initialization.
* All bean properties of this servlet will have been set before this
* method is invoked.
* <p>This default implementation is empty.
* @throws ServletException if subclass initialization fails
*/
protected void initServletBean() throws ServletException {
}
//用于给子类重写方法的钩子

GenericServlet类是属于Serlvet的包里定义的,所以Servlert与Spring的桥梁也就是GenericServlet类。HttpServletBean是属于GenericServlet类的实现,其他框架想要接入Servlet框架就要实现GenericServlet类的init()方法。SpringMVC是这样Strust应该也是这样。GenericServlet实现Servlet的生命周期。也就是init(ServletConfig config)方法的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

package javax.servlet;

import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.ResourceBundle;

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;

public GenericServlet() {
}

public void destroy() {
}

public String getInitParameter(String name) {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameter(name);
}
}

public Enumeration<String> getInitParameterNames() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameterNames();
}
}

public ServletConfig getServletConfig() {
return this.config;
}

public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}

public String getServletInfo() {
return "";
}

public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
//其他框架的接入点
public void init() throws ServletException {
}

public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}

public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}

public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

public String getServletName() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletName();
}
}
}

结合上面Servlet的生命周期,我们就能知道在DispatcherServlet init()的时候,Spring的容器就已经启动好了。

总结

总体捋一下流程。首先是配置web.xml文件,然后Tomcat在启动过程中会读取web.xml文件中的配置初始化最开始的servlet(DispatcherServlert)创建Servlet对象,当然这个地方你可以指定一个webApplicationContext。刚刚创建是没有webApplicationContext对象。然后Servlet开始执行init(ServletConfig config)方法(Servlet生命周期)然后调用HttpServletBean类中initServletBean()的方法去把一些生成Bean的配置都获取到,然后创建webApplicationContext对象这,但是HttpServletBean类没有这个类的实现(Spring做了进一步的扩展,其他以来Spring的框架也可以在实现webApplicationContext的创建),于是在FrameworkServlet类的initServletBean()方法上去查找和创建webApplicationContext。如果webApplicationContext不存在会调用FrameworkServlet类的createWebApplicationContext()方法,默认创建XmlWebApplicationContext.class。然后在调用FrameworkServlet类调用configureAndRefreshWebApplicationContext()方法时在启动容器(这个地方会有个递归调用)。

细心的同学发现还有我只说了configureAndRefreshWebApplicationContext()方法初始化webApplicationContext,没有说WebApplicationInitializer相关初始化webApplicationContext。这个我们后面会说,关于SpringMVC父子容器的问题。

1
2
3
4
/*
@see #configureAndRefreshWebApplicationContext
@see org.springframework.web.WebApplicationInitializer
*/

WebApplicationInitializer这个在Servlet3.0之前版本都是不能使用的,有了这个使SpringBoot创建webApplicationContext变的更方便。