spring源码之初始化
前面我们探讨了struct的源码及其工作原理。本篇开始,我们将来探讨spring的源码及其工作原理。struct2的核心对象是dispacher,在struct2初始化的时候,初始化一个dispacher对象,当有request请求来的时候,调用dispacher的方法来处理和执行request请求。而spring的核心是ioc容器,如果给我们设计,那spring的初始化是不是都要初始化一个容器对象?然后在运行时就从容器中取出对象?就好比于struct2的container容器,在初始化的的把配置文件中配置的对象放入到容器中,然后在处理action请求的时候,通过@inject依赖注入。同样,spring在初始化的时候也会把配置中的对象放入到容器中(spring把容器称为beanFactory或者是applicationContext,下面会介绍这两个的区别),下面就让我们看看spring是不是这样实现的。
那么spring是从哪里开始的呢?struct2用到了过滤器filter,而spring用到了监听器listener.
在web.xml中配置一个监听器listener-ContetLoaderListener.
<!--【代码清单】:在web.xml中配置监听器listener--> <!-- 配置spring初始化监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
在tomcat启动的时候,会自动运行配置的监听器ContextLoaderListener的contextInitialized()方法(这是因为ContextLoaderListener继承了ServletContextListener的接口,具体原理参考tomcat的运行机制)。那么,在这个方法中,spring到底干了些什么呢?我们来看看这个方法
//【代码清单】:contextInitialized()方法 publicvoid contextInitialized(ServletContextEvent event) { //创建一个ContextLoader this.contextLoader =createContextLoader(); //初始化ioc容器 this.contextLoader.initWebApplicationContext(event.getServletContext()); }
在这个方法中主要是实例化了一个contextLoader,然后通过这个contextLoader初始化一个webApplicationContext,这个webApplicationContext俗称就是spring的ioc容器。这里也论证了我们上面的猜想:spring的初始化,就是初始化一个IOC容器,当然,这个过程中,spring会把配置文件中的对象放入到容器,这里也会涉及到解析配置文件。
spring的ioc容器有很多,不同的应用层面有不同的ioc服务,如接口的有BeanFactoy和ApplicationContext,继承这两个接口类又有很多,如继承applicationContext的有ConfigurableApplicationContext和WebApplicationContext,而实现这些接口的类也有很多,如实现webApplicationContext的有xmlWebApplictationContext,实现ConfigurableApplicationContext的有FileSystemApplicationContext。这些都是针对不用应用层面提供的ioc服务。FileSystemApplicationContext是在文件目录下解析xml 文件,而xmlWebApplictationContext是在xml配置中获取xml路径并解析。
针对这么多的ioc容器,tomcat启动的时候,到底初始化哪个ioc容器?这就需要一个类来对这些IOC容器进行管理,并决定在tomcat启动的时候,决定加载哪个ioc容器,这个类就是ContextLoader。
实际web项目开发是在web.xml中配置applicationContext.xml文件的路径。因此本文以webapplicationContext作主要解析。
<!--代码清单:web.xml中配置 applicationContext.xml路径--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext*.xml</param-value> </context-param>
classpath:表示从类路劲加载配置文件,详细讲解,请看spring的策略管理。
//【代码清单】:ContextLoader publicclass ContextLoader { //初始化IOC容器 WebApplicationContext initWebApplicationContext(); //获得当前ioc容器 WebApplicationContext getCurrentWebApplicationContext(); //关闭ioc容器 void closeWebApplicationContext }
ContextLoader主要提供了初始化ioc容器,获取当前ioc容器,和关闭当前ioc容器的几个方法。通俗地说,contextLoader就是一个管理ioc容器的类。我们可以创建自己的ContextLoader来加载自己想要的ioc容器。
//【代码清单】:createContextLoader() protected ContextLoadercreateContextLoader() { //实例化一个ContextLoader对象 return new ContextLoader(); }
在createContextLoader()方法中主要是new了一个ContextLoader实例。
初始化IOC容器主要是调用了ContextLoader. initWebApplicationContext()方法。
//【代码清单】:initWebApplicationContext() public WebApplicationContextinitWebApplicationContext (ServletContext servletContext) throwsIllegalStateException, BeansException { // 因为webApplicationContext最后要保存到request的*.ROOT常量中,所以这里先判断这个这个变量是否为null,为null则抛出异常。此处代码省略。 try { // 1.获取根上下文, ApplicationContextparent = loadParentContext(servletContext); //2. 创建webApplicationContext,完成初始化后添加到根上下文中 this.context =createWebApplicationContext(servletContext, parent); //3保存到servletContext.ROOT中 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); // 4.添加到当前线程中 currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context); // 此处异常信息,代码省略。 returnthis.context; }
//【代码清单】:loadParentContext() LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector"; LOCATOR_FACTORY_KEY_PARAM = "parentContextKey"; protected ApplicationContext loadParentContext(ServletContextservletContext)throws BeansException { // 1.从ServletContext获取上面那2个常量值 servletContext.getInitParameter(); // 2.如果parentContextKey的值为空,则返回一个空的applicationContext,否则 if (parentContextKey!= null) { //3.先判断locatorFactorySelector是否为null,如果是null,则用默认值classpath*:beanRefContext.xml,如果不为null,则根据locatorFactorySelector实例化一个BeanFactoryLocator BeanFactoryLocatorlocator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); // 4.然后用BeanFactoryLocator. useBeanFactory()方法实例化一个parentContextRef .ApplicationContext是parentContextRef的一个field. parentContextRef.applicationContext= new ClassPathXmlApplicationContext(parentContextKey) this.parentContextRef = locator.useBeanFactory(parentContextKey); //5. return parentContextRef.applicationContext parentContext = (ApplicationContext) this.parentContextRef.getFactory(); } return parentContext; }
//【代码清单】:createWebApplicationContext() protectedWebApplicationContext createWebApplicationContext( ServletContextservletContext, ApplicationContext parent) throws BeansException { //1.判断载入的applicationContext类型,如果没有则默认使用继承WebApplicationContext接口的类。 ClasscontextClass = determineContextClass(servletContext); //判断contextClass是否是ConfigurableWebApplicationContext.class类型,不是则抛出异常。此处代码省略。ConfigurableWebApplicationContext是一个继承了webApplicationContext和ConfiguteApplicationContext接口的接口。、 //2.根据contextClass实例化一个ConfigurableWebApplicationContext ConfigurableWebApplicationContextwac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); //3.添加根上下文 wac.setParent(parent); //4.添加servletContext wac.setServletContext(servletContext); //5.获取web.xml中配置的contextConfigLocation参数 wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM)); //6.定制Context,这是一个什么也不做的方法,用于拓展,可以忽略 customizeContext(servletContext,wac); //7.解析applicationContext.xml,完成初始化 wac.refresh(); return wac; }
//【代码清单】:determineContextClass() protected Class determineContextClass(ServletContextservletContext) throws ApplicationContextException { //1.获取我们在web.xml中配置的contextClass参数,这个参数可以可以决定我们需要载入的applicationContext类型 StringcontextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName!= null) { try { return ClassUtils.forName(contextClassName); } } else { //2.如果没配置有,则使用继承webApplicationContext接口的Class类 contextClassName= defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName,ContextLoader.class.getClassLoader()); } } }
//【代码清单】:instantiateClass(contextClassName) publicstatic Object instantiateClass(Class clazz) throws BeanInstantiationException { try { //重载instantiateClass方法 return instantiateClass(clazz.getDeclaredConstructor((Class[])null), null); } }
在instantiateClass(contextClassName)方法中重载了instantiateClass方法
//【代码清单】:instantiateClass(constructor,args) publicstatic Object instantiateClass(Constructor ctor, Object[] args) throws BeanInstantiationException { try { //setAccessible(true) ReflectionUtils.makeAccessible(ctor); //实例化 return ctor.newInstance(args); } }
//【代码清单】:setParent() publicvoid setParent(ApplicationContextparent) { this.parent = parent; }
//【代码清单】:setServletContext() publicvoid setServletContext(ServletContext servletContext) { this.servletContext = servletContext; }
CONFIG_LOCATION_PARAM = "contextConfigLocation"; servletContext.getInitParameter(CONFIG_LOCATION_PARAM)
获的web.xml配置contextConfigLocation的参数
<!--【代码清单】:web.xml的contextConfigLocation配置参数--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext*.xml</param-value> </context-param>
处理参数
//【代码清单】:处理参数 publicvoid setConfigLocation(String location) { //String CONFIG_LOCATION_DELIMITERS = ",; \t\n"; //“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)” setConfigLocations(StringUtils.tokenizeToStringArray(location,CONFIG_LOCATION_DELIMITERS)); }
tokenizeToStringArray()方法是处理“classpath:applicationContext*.xml”这个字符串成一个数组,然后调用setConfigLocations(String[]locations)方法。
//【代码清单】:tokenizeToStringArray() publicstatic String[]tokenizeToStringArray( Stringstr, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { StringTokenizerst = new StringTokenizer(str, delimiters); Listtokens = new ArrayList(); while(st.hasMoreTokens()) { //分割 Stringtoken = st.nextToken(); //前后去掉空格。此处代码省略 if (!ignoreEmptyTokens || token.length() > 0) { //添加到数组 tokens.add(token); } } //转字符数组 returntoStringArray(tokens); }
把配置文件信息,保存到容器中
//【代码清单】:setConfigLocations(String[] locations) //configLocations只是一个字符数组 private String[] configLocations; publicvoid setConfigLocations(String[] locations) { if (locations != null) { this.configLocations = newString[locations.length]; for (int i = 0; i <locations.length; i++) { //循环遍历,赋给configLocations this.configLocations[i] =resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
AbstractRefreshableWebApplicationContext是实现了
ConfigurableWebApplicationContext接口的类,但是
AbstractRefreshableWebApplicationContext中 并没有refresh()方法。当我们一层一层找它的父类的时候,在父类AbstractApplicationContext中找到了refresh()这个方法。
//【代码清单】:AbstractApplicationContext.refresh() publicvoid refresh() throws BeansException,IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1.容器启动的预先准备,记录容器启动的时间和标记. prepareRefresh(); //2.创建BeanFactory,如果已有就销毁,没有就创建.此类实现了对BeanDefinition的装载 ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory(); //3.配置BeanFactory标准上下文特性,如类装载器、postProcesser等 prepareBeanFactory(beanFactory); try { // 4.在bean被装载后,提供一个修改BeanFactory的入口 postProcessBeanFactory(beanFactory); // 5.调用postProceessBeanFactory invokeBeanFactoryPostProcessors(beanFactory); //6.注册用于拦截bean创建过程中的BeanPostProcessors registerBeanPostProcessors(beanFactory); // 7.Initialize message source for this context. initMessageSource(); // 8.Initialize event multicaster for this context. initApplicationEventMulticaster(); //9.Initialize other special beans in specific contextsubclasses. onRefresh(); //10. 注册监听器 registerListeners(); //11.完成容器的初始化,里面的preInstantiateSingletons完成单例对象的创建 finishBeanFactoryInitialization(beanFactory); // 12.Last step: publish corresponding event. finishRefresh(); } } }
3.总结
综上所述,spring的初始化其实就是创建一个configurateWebApplicationContext对象,然后把这个对象放到servletContext和线程中。而创建这个webApplcationContext的过程,其实就是获取配置文件并解析这个配置文件的过程。上面讨论了spring已经从web.xml中获取了applicationContext.xml配置的文件信息,下篇博文,我们将探讨,refresh()这个方法是如何解析配置文件和完成初始化的。