转载自 http://ljbal.iteye.com/blog/497314
前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spring的IoC容器在web容器如何启动和起作用的并不清楚。所以就抽时间看一下spring的源代码,借此了解它的原理。
我们知道,对于使用Spring的web应用,无须手动创建Spring容器,而是通过配置文件,声明式的创建Spring容器。因此在Web应用中创建Spring容器有如下两种方式:
1. 直接在web.xml文件中配置创建Spring容器。
2. 利用第三方MVC框架的扩展点,创建Spring容器。
其实第一种方式是更加常见。为了让Spring容器随Web应用的启动而启动,有如下两种方式:
1. 利用ServletContextListener实现。
2. 利用load-on-startup Servlet实现。
Spring提供ServletContextListener的一个实现类ContextLoaderListener,该类可以作为Listener 使用,它会在创建时自动查找WEB-INF下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,则只需在web.xml文件中增加以下配置片段就可以了。
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
如果有多个配置文件需要载入,则考虑使用<context-param...>元素来确定配置文件的文件名。ContextLoaderListener加载时,会查找名为contentConfigLocation的初始化参数。因此,配置<context-param...>时就指定参数名为contextConfigLocation。
带多个配置文件的web.xml文件如下:
- <context-param>
- <param-name>contextLoaderListener</param-name>
- <param-value>
- WEB-INF/*.xml, classpath:spring/*.xml
- </param-value>
- </context-param>
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
多个配置文件之间用“,”隔开。
下面我们来看它的具体实现过程是怎样的,首先我们从ContextLoaderListener入手,它的代码如下:
- public class ContextLoaderListener implements ServletContextListener
- {
-
- private ContextLoader contextLoader;
-
-
-
-
-
- public void contextInitialized(ServletContextEvent event)
- {
- this.contextLoader = createContextLoader();
- this.contextLoader.initWebApplicationContext(event.getServletContext());
- }
-
-
-
-
-
- protected ContextLoader createContextLoader()
- {
- return new ContextLoader();
- }
- ................
-
- }
我们看到初始化web application context的时候,首先通过new ContextLoader()创建一个contextLoader,
new ContextLoader()具体做了什么事呢?ContextLoader的代码片段:
- static {
- try {
-
-
-
-
- ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
-
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- }
- catch (IOException ex) {
- throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
- }
- }
代码注释里面已经说得很清楚了,很容易理解吧?嘿嘿......
再下来我们再看一下initWebApplicationContext方法的实现过程:
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
- throws IllegalStateException, BeansException {
-
-
-
-
-
-
-
- if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
- throw new IllegalStateException(
- "Cannot initialize context because there is already a root application context present - " +
- "check whether you have multiple ContextLoader* definitions in your web.xml!");
- }
-
- servletContext.log("Initializing Spring root WebApplicationContext");
- if (logger.isInfoEnabled()) {
- logger.info("Root WebApplicationContext: initialization started");
- }
- long startTime = System.currentTimeMillis();
-
- try {
-
-
- ApplicationContext parent = loadParentContext(servletContext);
-
-
-
-
- this.context = createWebApplicationContext(servletContext, parent);
-
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
-
- if (logger.isDebugEnabled()) {
- logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
- WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
- }
- if (logger.isInfoEnabled()) {
- long elapsedTime = System.currentTimeMillis() - startTime;
- logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
- }
-
- return this.context;
- }
- catch (RuntimeException ex) {
- logger.error("Context initialization failed", ex);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
- throw ex;
- }
- catch (Error err) {
- logger.error("Context initialization failed", err);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
- throw err;
- }
- }
从上面的代码可以看出,我们创建好的applicationContext容器会放在servletContext中。servletContext是什么 呢?
在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的基础上,和web MVC相关还会有一个上下文来保存控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。
从initWebApplicationContext中可以看到真正创建applicationContext容器是由createWebApplicationContext方法来实现的,它的代码如下:
- protected WebApplicationContext createWebApplicationContext(
- ServletContext servletContext, ApplicationContext parent) throws BeansException
- {
-
- Class contextClass = determineContextClass(servletContext);
-
- if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))
- {
- throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
- "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
- }
-
-
- ConfigurableWebApplicationContext wac =
- (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
- wac.setParent(parent);
- wac.setServletContext(servletContext);
-
- String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
- if (configLocation != null)
- {
- wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
- ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
- }
-
- wac.refresh();
- return wac;
- }
createWebApplicationContext方法实现步骤为:
1. 首先决定要创建的applicationContext容器的类
2. 实例化applicationContext容器
但它是如何决定要创建的容器类呢?我们看一下determineContextClass方法:
- protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException
- {
-
- String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
-
- if (contextClassName != null)
- {
- try {
- return ClassUtils.forName(contextClassName);
- }
- catch (ClassNotFoundException ex) {
- throw new ApplicationContextException(
- "Failed to load custom context class [" + contextClassName + "]", ex);
- }
- }
-
-
- else
- {
- contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
- try
- {
- return ClassUtils.forName(contextClassName);
- }
- catch (ClassNotFoundException ex)
- {
- throw new ApplicationContextException(
- "Failed to load default context class [" + contextClassName + "]", ex);
- }
- }
- }
该方法首先判断从web.xml文件的初始化参数CONTEXT_CLASS_PARAM(的定义为public static final String CONTEXT_CLASS_PARAM = "contextClass";)获取的的类名是否存在,如果存在,则容器的Class;否则返回默认的
Class。如何获取默认的容器Class,注意看创建contextLoader时的代码注释就知道了。
由此看来,spring不仅有默认的applicationContext的容器类,还允许我们自定义applicationContext容器类,不过Spring不建义我们自定义applicationContext容器类。
好了,这就是spring的IoC容器在web容器如何启动和起作用的全部过程。细心的朋友可以看出创建applicationContext容器的同时会初始化配置文件中定义的bean类,createWebApplicationContext方法中的wac.refresh();这段代码就是用来初始化配置文件中定义的bean类的。它具体的实现过程现在还没完全搞清楚,等搞清楚了再跟大家分享!