Spring启动<一>——ContextLoaderListener
??? 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实例化所做的工作以及设置属性时的工作。