org.springframework.web.context.ContextLoaderListener继承自org.springframework.web.context.ContextLoader,同时实现了javax.servlet.ServletContextListener接口,因此在系统启动的时候,首先会调用ContextLoaderListener方法中的public void contextInitialized(ServletContextEvent event)方法,该方法源代码如下所示:
- private ContextLoader contextLoader;
- /**
- * Initialize the root web application context.
- */
- public void contextInitialized(ServletContextEvent event) {
- this.contextLoader = createContextLoader();
- if (this.contextLoader == null) {
- this.contextLoader = this;
- }
- this.contextLoader.initWebApplicationContext(event.getServletContext());
- }
ContextLoaderListener基本上是对ContextLoader的代理。在该方法的第一步是创建一个ContextLoader,通过查看源代码知道,该方法createContextLoader()是个Deprecated的方法,这一步返回值为null。最重要的是这最后一步:
- this.contextLoader.initWebApplicationContext(event.getServletContext());
这一步直接调用了其父类中的public WebApplicationContext initWebApplicationContext(ServletContext servletContext)方法。该方法通过web.xml配置文件中contextClass参数和contextConfigLocation的值来初始化给定的ServletContext。我们来看看这个方法做了哪些工作。下面代码中刨除了日志相关代码,只保留了最核心的操作:
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
- 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!");
- }
- try {
- // 第一步:Determine parent for root web application context, if any.
- ApplicationContext parent = loadParentContext(servletContext);
- // 第二步:Store context in local instance variable, to guarantee that
- // it is available on ServletContext shutdown.
- this.context = createWebApplicationContext(servletContext, parent);
- //第三步
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
- ClassLoader ccl = Thread.currentThread().getContextClassLoader();
- if (ccl == ContextLoader.class.getClassLoader()) {
- currentContext = this.context;
- }
- else if (ccl != null) {
- currentContextPerThread.put(ccl, this.context);
- }
- return this.context;
- }
- catch (RuntimeException ex) {
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
- throw ex;
- }
- catch (Error err) {
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
- throw err;
- }
- }
跳过前面的检测部分,我们直接看第一步,这一步调用了ContextLoader类中另外一个方法以判断是否存在root context的parent context,查看该方法的注释,有这么一句话:“For pure web applications, there is usually no need to worry about
having a parent context to the root web application context.”通过注释我们知道,一般情况下我们是用不到parent context,因此也不用考虑parent context,因此这一步我们得到的返回值会是null。(对于使用parent context的情况日后再去研究)。
且看第二步和第三步,分别是创建context和保存context到本地实例变量。跳过第二步,我们先看第三步。
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
上面的代码将context的引用保存到了ServletContext中,从而保证其他对象都消亡而ServletContext还存在的时候context仍然是可见的。
下面来看关键的第二步。第二步中调用了另外一个方法,其核心源代码如下:
- protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
- //第一步
- Class<?> contextClass = determineContextClass(sc);
- //第二步
- 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);
- //Assign the best possible id value.
- if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
- // Servlet <= 2.4: resort to name specified in web.xml, if any.
- String servletContextName = sc.getServletContextName();
- wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
- ObjectUtils.getDisplayString(servletContextName));
- }
- else {
- // Servlet 2.5's getContextPath available!
- try {
- String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc);
- wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
- ObjectUtils.getDisplayString(contextPath));
- }
- catch (Exception ex) {
- throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex);
- }
- }
- wac.setParent(parent);
- wac.setServletContext(sc);
- wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
- customizeContext(sc, wac);
- wac.refresh();
- return wac;
- }
我们且看第一步,这一步调用了determineContextClass方法,查看该方法的注释和源代码我们知道,这一步的意义在于返回系统设定的WebApplicationContext接口实现类,如果开发者没有指定该类,这默认使用org.springframework.web.context.support.XmlWebApplicationContext。context的实现类由web.xml中的contextClass参数指定。这一步我们假定返回的是系统的默认值。
第二步是个判断语句,我们可以看出这一步用来判断第一步返回的Class是否实现了org.springframework.web.context.ConfigurableWebApplicationContext接口,该接口继承自org.springframework.web.context.WebApplicationContext和org.springframework.context.ConfigurableApplicationContext接口。
第三步及之后的语句我们可以到,系统所做的是实例化org.springframework.web.context.support.XmlWebApplicationContext,并进行一些属性的设置。之后,整个启动过程也就完成了,下一篇我们将详细看看启动过程中,XmlWebApplicationContext实例化所做的工作以及设置属性时的工作。