spring web服务在
tomcat容器中启动的流程
在介绍
spring启动流程之前,先介绍
2个概念
servlet ,
ServletContext。
servlet 是
java web服务的核心组件,可以简单理解为
servlet是能接收并处理
web请求的服务(典型的就是
http请求)
ServletContext 是生成并维护
servlet的上下文,存在于特定容器之中,如
tomcat,
jetty,
resin等等
容器启动过程中解析
web.xml 文件,它描述了一个
web服务的关键信息,一般情况下包括
servlet 、
filter、
listener 。
容器启动时将通知相关的
listener,一般情况下
spring环境就是这个时候引入的。
执行相关
Servlet(如果设置为容器启动时执行)
filter初始化并执行
init方法
filter在
servlet接收请求过程中之前都会执行
下面介绍
spring 是如何在
web环境中被初始化的
StandardContext.listenerStart() 启动
web.xml 中配置的监听器(这个监听器在容器初始化时执行)
这里的
StandardContext就是
tomcat容器中的
servlet环境
从上面可以看出,启动过程为,
tomcat容器读取
web.xml 初始化
servlet容器
StandardContext,配置
listener,
filter,
servlet,初始化成功后触发相应的监听器,
servlet(如果设置为
load-on-startup)
现在看集成
spring的
web项目一般的
web.xml示例:
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
web-app
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
=
"http://java.sun.com/xml/ns/javaee"
xmlns:web
=
"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation
=
"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version
=
"3.0"
>
<
context-param
>
<
param-name
>
webAppRootKey
param-name
>
<
param-value
>
zhixiao.root
param-value
>
context-param
>
<
session-config
>
<
session-timeout
>
10
session-timeout
>
session-config
>
<
welcome-file-list
>
<
welcome-file
>
index.jsp
welcome-file
>
welcome-file-list
>
<
display-name
>
parking Created Web Application
display-name
>
<
context-param
>
<
param-name
>
contextConfigLocation
param-name
>
<
param-value
>
/WEB-INF/spring/*.
xml
param-value
>
context-param
>
<
listener
>
<
listener-class
>
org.springframework.web.util.Log4jConfigListener
listener-class
>
listener
>
<
listener
>
<
listener-class
>
org.springframework.web.context.ContextLoaderListener
listener-class
>
listener
>
<
listener
>
<
listener-class
>
org.springframework.web.util.IntrospectorCleanupListener
listener-class
>
listener
>
<
filter
>
<
filter-name
>
encodingFilter
filter-name
>
<
filter-class
>
org.springframework.web.filter.CharacterEncodingFilter
filter-class
>
<
init-param
>
<
param-name
>
encoding
param-name
>
<
param-value
>
UTF-8
param-value
>
init-param
>
<
init-param
>
<
param-name
>
forceEncoding
param-name
>
<
param-value
>
true
param-value
>
init-param
>
filter
>
<
filter-mapping
>
<
filter-name
>
encodingFilter
filter-name
>
<
url-pattern
>
/*
url-pattern
>
filter-mapping
>
<
filter
>
<
display-name
>
RedisSessionFilter
display-name
>
<
filter-name
>
RedisSessionFilter
filter-name
>
<
filter-class
>
com.qhyu.zhixiao.session.RedisSessionFilter
filter-class
>
filter
>
<
filter-mapping
>
<
filter-name
>
RedisSessionFilter
filter-name
>
<
url-pattern
>
/*
url-pattern
>
<
dispatcher
>
REQUEST
dispatcher
>
<
dispatcher
>
FORWARD
dispatcher
>
filter-mapping
>
<
filter
>
<
filter-name
>
ipFilter
filter-name
>
<
filter-class
>
com.qhyu.zhixiao.filter.IpFilter
filter-class
>
filter
>
<
filter-mapping
>
<
filter-name
>
ipFilter
filter-name
>
<
url-pattern
>
/*
url-pattern
>
<
dispatcher
>
REQUEST
dispatcher
>
<
dispatcher
>
FORWARD
dispatcher
>
filter-mapping
>
<
servlet
>
<
servlet-name
>
springmvc
servlet-name
>
<
servlet-class
>
org.springframework.web.servlet.DispatcherServlet
servlet-class
>
<
load-on-startup
>
1
load-on-startup
>
servlet
>
<
servlet-mapping
>
<
servlet-name
>
springmvc
servlet-name
>
<
url-pattern
>
/
url-pattern
>
servlet-mapping
>
<
servlet-mapping
>
<
servlet-name
>
default
servlet-name
>
<
url-pattern
>
*.
css
url-pattern
>
servlet-mapping
>
web-app
>
下面重点看
ContextLoaderListener
这个
listener在
tomcat容器启动时执行
public
class
ContextLoaderListener
extends
ContextLoader
implements
ServletContextListener {
public
ContextLoaderListener() {}
public
ContextLoaderListener(WebApplicationContext
context
) {
super
(
context
);
}
/**
* Initialize the root web application context.
*/
@Override
public
void
contextInitialized(ServletContextEvent
event
) {
initWebApplicationContext(
event
.getServletContext());
}
@Override
public
void
contextDestroyed(ServletContextEvent
event
) {
closeWebApplicationContext(
event
.getServletContext());
ContextCleanupListener.
cleanupAttributes
(
event
.getServletContext());
}
}
initWebApplicationContext
方法就是初始化
spring
的入口,参数就是
tomcat
容器初始化的
servlet
进入
initWebApplicationContext
方法主要干了两件事
1.
初始化
WebApplicationContext
WebApplicationContext context = null;
if
(
this
.
context
==
null
) {
this
.
context
= createWebApplicationContext(
servletContext
);
}
通过
java
反射机制生成
ConfigurableWebApplicationContext
类对象,它就是
spring
的
bean
生成运行环境(
BeanFactory
容器
)
2.
刷新初始化
webApplication
ConfigurableWebApplicationContext
cwac
= (ConfigurableWebApplicationContext)
this
.
context
;
configureAndRefreshWebApplicationContext(
cwac
,
servletContext
);
生成好
spring
上下文对象后,进入
configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext
wac
, ServletContext
sc
)
方法
,
主要执行代码如下:
wac
.setServletContext(
sc
);
String
configLocationParam
=
sc
.getInitParameter(
CONFIG_LOCATION_PARAM
);
if
(
configLocationParam
!=
null
) {
wac
.setConfigLocation(
configLocationParam
);//
为
spring
上下文设置
spring bean
配置文件路径
}
ConfigurableEnvironment
env
=
wac
.getEnvironment();
if
(
env
instanceof
ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)
env
).initPropertySources(
sc
,
null
);
}
customizeContext(
sc
,
wac
);
wac
.refresh();
这里有一个注意的地方,
ConfigurableEnvironment
生成的地方,
wac
.setConfigLocation(
configLocationParam
);
时根据
configLocationParam
设置配置参数路径时就会初始化
StandardServletEnvironment
(
ConfigurableEnvironment
的子类
)
进入
AbstractApplicationContext.
refresh()
方法
public
void
refresh()
throws
BeansException, IllegalStateException {
synchronized
(
this
.
startupShutdownMonitor
) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory
beanFactory
= obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(
beanFactory
);
try
{
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(
beanFactory
);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(
beanFactory
);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(
beanFactory
);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(
beanFactory
);
// Last step: publish corresponding event.
finishRefresh();
}
catch
(BeansException
ex
) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(
ex
);
// Propagate exception to caller.
throw
ex
;
}
finally
{
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
(1)prepareRefresh()
// Initialize any placeholder property sources in the context environment
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
(2) obtainFreshBeanFactory();
此方法负责创建
beanFactory
并根据
spring xml
配置文件初始化得到
BeanDefintions
这里主要看
XmlWebApplicationContext
类的
loadBeanDefinitions
方法
从上图看一看出,是如何一步一步的解析
xml
文件的
主要代码在
NamespaceHandler
中
,根据命名空间得到相应的处理器来解析相关配置文件中的标签
tag
实例
:
namespaceUri=
http://www.springframework.org/schema/context
得到的
handler
为:
org.springframework.context.config.ContextNamespaceHandler
其实就是从
spring
的
jar
包配置文件中获取的
public
class
ContextNamespaceHandler
extends
NamespaceHandlerSupport {
@Override
public
void
init() {
registerBeanDefinitionParser(
"property-placeholder"
,
new
PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser(
"property-override"
,
new
PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser(
"annotation-config"
,
new
AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser(
"component-scan"
,
new
ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser(
"load-time-weaver"
,
new
LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser(
"spring-configured"
,
new
SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser(
"mbean-export"
,
new
MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser(
"mbean-server"
,
new
MBeanServerBeanDefinitionParser());
}
}
应该可以明显看出,
xml
中的标签就是通过这些
handler
来处理的
通过
handler
中注册的
parser
来解析
tag
下面再来看下是如何解析的
通过
tag
名称
,得到相应的解析器并做相应的处理
上面实例就是解析
property-placeholder
标签的
parser
回到
refresh()
方法,可以看到
obtainFreshBeanFactory();
其实已经干了大部分工作了,创建
spring bean
上下文
beanFactory
并解析
xml
得到
beanDefintion
初始化上下文。
下一步执行
prepareBeanFactory(
beanFactory
);
方法
这个方法主要是对
beanFactory
做一些初始化工作
1
)设置
classLoader
2
)设置
SpEL
表达式解析
3
)设置
ResourceEditorRegistrar
(在获取
bean
中设置
value
)
4
)设置前置处理器
beanFactory
.addBeanPostProcessor(
new
ApplicationContextAwareProcessor(
this
));
5
)设置一些不需要
autowired
的
bean
beanFactory
.ignoreDependencyInterface(ApplicationContextAware.
class
);
ApplicationContextAware
子类不通过
autowired
方式注入
6
)设置和环境相关的
beans
prepareBeanFactory
之后执行
postProcessBeanFactory
主要设置了一些前置处理器。
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(
beanFactory
);
后续就是注册资源文件,监听器,实例化一些非延迟加载的
bean
,最后广播相关事件
over
总结
:
spring
家族中所有的部件都是基于
spring bean
容器的,了解
beanFactory
上下文的结构和原理对理解
spring
十分重要,本文主要从整体上描述了
spring
如何在
web
环境中初始化启动,从源码上给出了一个简要的流程描述,希望读者仔细阅读后能对
spring
的
bean
容器有一个系统的了解。
参考:
1.
http://www.ibm.com/developerworks/cn/java/j-lo-servlet/
2.http://www.cnblogs.com/RunForLove/p/5688731.html