Spring bean前后文环境在web环境的初始化概述
Spring bean上下文环境在web环境的初始化概述
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示例:
<?xmlversion="1.0"encoding="UTF-8"?>
<web-appxmlns: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 timeout, minutes unit -->
<session-config>
<session-timeout>10</session-timeout>
</session-config>
<!-- default home page -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<display-name>parking Created Web Application</display-name>
<!-- sprint/* -->
<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> <!-- 可以去掉,因为spring mvc 的DispatchServlet 支持request session 作用范围的bean -->
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<!-- Spring 刷新Introspector防止内存泄露 -->
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!-- encoding -->
<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>
<!-- redis session接管 -->
<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>
<!-- ip checking -->
<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>
<!-- springmvc -->
<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容器启动时执行
publicclass ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
publicvoid contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
publicvoid 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 (envinstanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
这里有一个注意的地方,ConfigurableEnvironment生成的地方,
wac.setConfigLocation(configLocationParam); 时根据 configLocationParam设置配置参数路径时就会初始化StandardServletEnvironment(ConfigurableEnvironment的子类)
进入AbstractApplicationContext.refresh()方法
publicvoid 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.
throwex;
}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包配置文件中获取的
publicclass ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
publicvoid 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