详解contextConfigLocation|Spring启动过程详解
spring的应用初始化流程一直没有搞明白,刚刚又碰到了相关的问题。决定得好好看看这个流程。我们在开发spring的项目当中基本上都会在web.xml通过:
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/conf/application-*.xml </param-value> </context-param>
来初始化各个spring的配置文件,但是我们只是知道这段代码的功能, 并不是很清楚我们配置了这段代码之后为什么就能去初始化配置文件。当然我们还会加上:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
这一个listener,我首先就会想contextConfigLocation这个一定能在ContextLoaderListener这个类当中找到,打开了源码,这个listener是实现了ServletContextListener这个接口的,这个接口只有两个方法:
public interface ServletContextListener extends EventListener { public abstract void contextInitialized(ServletContextEvent servletcontextevent); public abstract void contextDestroyed(ServletContextEvent servletcontextevent); }
而且它是继承了EventListener这个接口的,打开这个接口的代码让我大吃一惊,里面没有方法啥都没有:
package java.util; public interface EventListener { }
而且还是java.util包下的,并不是spring之中的东西。
这样找了之后没有找到,往回退到ContextLoaderListener这个类的方法上,contextInitialized方法是用来初始化上下文的:
public void contextInitialized(ServletContextEvent event) { contextLoader = createContextLoader(); contextLoader.initWebApplicationContext(event.getServletContext()); }
方法中有个createContextLoader方法:
protected ContextLoader createContextLoader() { return new ContextLoader(); }
这个方法返回了一个ContextLoader实例,进入到ContextLoader类中,按ctrl+f来寻找contextConfigLocation,这时没有出现电脑的咚的声音,找到了它:
protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent) throws BeansException { Class contextClass = determineContextClass(servletContext); if(!(org.springframework.web.context.ConfigurableWebApplicationContext.class).isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + (org.springframework.web.context.ConfigurableWebApplicationContext.class).getName() + "]"); } else { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setParent(parent); wac.setServletContext(servletContext); wac.setConfigLocation(servletContext.getInitParameter("<span style="color:#ff0000;">contextConfigLocation</span>")); customizeContext(servletContext, wac); wac.refresh(); return wac; } }
通过代码,ConfigurableWebApplicationContext设置了从servletContext获取到的参数的值,再进入ConfigurableWebApplicationContext的代码中,它只是一个接口,进入StaticWebApplicationContext的setConfigLocation方法:
public void setConfigLocation(String configLocation) { if(configLocation != null) throw new UnsupportedOperationException("StaticWebApplicationContext does not support config locations"); else return; }
这个方法中很奇怪,当参数不为空就抛出异常,查看spring的文档:The StaticWebApplicationContext
class does not support this method.说是此类不支持这个方法,这下子又卡住了。又要退回去,看这句:
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
pring使用BeanUtils来初始化contextClass这个类实例,contextClass是通过以下代码得到的:
protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException { String contextClassName = servletContext.getInitParameter("contextClass"); if(contextClassName != null) try { return ClassUtils.forName(contextClassName); } catch(ClassNotFoundException ex) { throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex); } contextClassName = defaultStrategies.getProperty((org.springframework.web.context.WebApplicationContext.class).getName()); try { return ClassUtils.forName(contextClassName, (org.springframework.web.context.ContextLoader.class).getClassLoader()); } catch(ClassNotFoundException ex) { throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex); } }
这里使用了反射,再来看BeanUtils的instantiateClass方法:
return instantiateClass(clazz.getDeclaredConstructor((Class[])null), null);
通过反射得到contextClass的构造方法。下面是instantiateClass方法的重载,主要是下面两句代码:
ReflectionUtils.makeAccessible(ctor); return ctor.newInstance(args);
ctor是通过反射得到的contextClass的构造方法,args是构造方法当中的参数。这里为null,说明new了contextClass的无参构造方法。
这时又要退回到determineContextClass 这个方法中,我们主要看:
contextClassName = defaultStrategies.getProperty((org.springframework.web.context.WebApplicationContext.class).getName());
这句代码,我们可以猜它是通过Properties的getProperty方法得到WebApplicationContext 的实例,这时我们又到了WebApplicationContext 这个接口当中,这个接口继承了ApplicationContext这个接口,我们都知道我们进行spring开发都会通过Application ctx=new FileSystemXmlApplicationContext("beans.xml");或ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml");或ServletContext servletContext = request.getSession().getServletContext();ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);这三种方法获得一个ApplicationContext,然后就可以对配置文件当中的bean进行操作了。所以这里我们基本上已经搞清楚初始化spring配置文件的流程了。
总结:通过查看这几个类的源代码,java的反射使用范围之广再次体现出来。如看了之后觉得有错误或者不同意见,欢迎提出来,我也是第一次才研究这个问题。