spring源码之解析配置文件原理
上篇博文提到,spring利用监听器实现在tomcat启动的时候实现初始化。在初始化的过程中,实例化一个webapplicationContext对象,然后利用这个对象的refresh()方法解析配置文件并完成初始化.这篇博文就详细来探讨下这个refresh()方法.
//代码清单:refresh() publicvoid refresh() throws BeansException,IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1.容器启动的预先准备,记录容器启动的时间和标记. prepareRefresh(); //2.创建BeanFactory,如果已有就销毁,没有就创建.此类实现了对BeanDefinition的装载 ConfigurableListableBeanFactory beanFactory = 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. } }
protected void prepareRefresh() { //记录容器启动的时间. this.startupDate = System.currentTimeMillis(); //记录标记. synchronized (this.activeMonitor) { this.active = true; } if (logger.isInfoEnabled()) { logger.info("Refreshing " + this); } }
这个方法主要是记录容器启动的时间和一些日志信息,对系统并没什么影响。
二.obtainFreshBeanFactory
这个方法就是本篇博文讨论的重点-解析配置文件applicationContext.xml
1.解析配置文件步骤
①创建一个beanFactory(application)
②beanFactory中添加一个读取配置信息的reader(DefaultBeanDefinitionDocumentReader)
③创建ioc配置文件抽象资源(Resouce)
④读取配置信息reader.load()
这个步骤不难理解。spring的核心是ioc容器,把配置文件中的对象放入容器中,那我们首先得有一个容器吧?所以这个步骤通俗地理解为:先创建一个盛放对象的容器(BeanFactory),然后创建一个解析器(reader),再获取配置文件信息(resouce),最后用解析器解析配置文件,把解析出来的对象放到这个容器中。
//1.先创建一个IOC容器 DefaultListabeBeanFactory factory=new DefaultListableBeanFactory(); //2.获取一个解析器 XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory); //3.获取资源文件 ClassPathResouce res=new ClassPathResource("beans.xml"); //4.解析配置文件 reader.loadBeanDefinitions(res);
正如struct2一样,解析配置文件,会把配置文件中的信息封装到不同的对象中。spring把不同的配置信息封装到beanDefinitions接口的不同实现类中。因为配置文件可能会配置在不同的地方,比如在web.xml,或者是要在file system中,又或者是在classpath目录下,针对这种情况,spring设计了一个resouce接口,来统一了这些路径。只要你实现了相应的resouce类,就能加载相应路径下的配置文件。具体请参考后面有关spring的策略模式的讲解。Spring的BeanFacotry是一个类工厂,使用它来创建各种类型的Bean,最主要的方法就是getBean(String beanName),该方法从容器中返回特定名称的Bean。
下面我们就来看看spring是如何实现上述的这些步骤的。
【代码清单】:obtainFreshBeanFactory() protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //创建一个IOC容器beanFactory,并解析配置文件 refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); return beanFactory; }
//【代码清单】:refreshBeanFactory() protected final void refreshBeanFactory()throws BeansException { //判断beanFactory是否已存在 if (hasBeanFactory()){ //存在就销毁beans destroyBeans(); //关闭工厂 closeBeanFactory(); } try { //不存在,就创建beanFactory, DefaultListableBeanFactory是beanFactory的实现类 DefaultListableBeanFactorybeanFactory = createBeanFactory(); customizeBeanFactory(beanFactory); //创建一个reader,默认是XmlBeanDefinitionReader,并解析,把对每个标签的解析结果都封装到相应的beanDefinition对象,然后把这个对象注册到DefaultListableBeanFactory loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } }
创建beanFactory,其实就是new 一个实现类。
//【代码清单】:createBeanFactory() protected DefaultListableBeanFactory createBeanFactory() { //DefaultListableBeanFactory是beanFactory的实现类 return new DefaultListableBeanFactory(getInternalParentBeanFactory()); }
//【代码清单】:loadBeanDefinitions(beanFactory) protectedvoid loadBeanDefinitions(DefaultListableBeanFactorybeanFactory) throws IOException { // 实例化一个reader XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //配置loader环境,也就是ioc配置文件抽象资源,ResourceLoder定位bean资源 beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(newResourceEntityResolver(this)); // 空方法 initBeanDefinitionReader(beanDefinitionReader); //载入bean信息,并对其进行处理 loadBeanDefinitions(beanDefinitionReader); }
这里创建了一个解析器xmlBeanDefinitonReader。看它的名字,就知道它的作用了。BeanDefinition是封装配置信息的对象,reader是读取,xml是配置文件,因此这个对象就是applictionContext.xml的解析器。下面的工作就是获取配置文件了。
//【代码清单】:loadBeanDefinitions(beanDefinitionReader) protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //获取web.xml中的contextConfigLocation的配置信息 String[] configLocations = getConfigLocations(); //循环遍历解析 if (configLocations != null) { for (int i = 0; i < configLocations.length; i++) { reader.loadBeanDefinitions(configLocations[i]); } } }
这个configLoactions是在监听器listener中就已经设置好的了
wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));
这里用get()方法,获取这一配置信息。因为,在web.xml中可以配置多个applicationContext.xml,所以这里循环遍历来解析。
<!-- 指定spring的配置文件,默认从web根目录寻找配置文件,classpath从类路径寻找,tomacat启动的时候,实例化spring容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext*.xml</param-value> </context-param>
上面说到,spring设计了一个resouce接口来管理配置文件。所以下面还得根据这个路径,来获得一个resouce对象。
//【代码清单】:reader.loadBeanDefinitions(String) publicint loadBeanDefinitions(Stringlocation, Set actualResources) throws BeanDefinitionStoreException { //得到当前的ResourceLoader,默认使用DefaultResourceLoader ResourceLoader resourceLoader = getResourceLoader(); //如果没有ResourceLoader则抛出异常,此处代码省略 if (resourceLoader instanceof ResourcePatternResolver) { // 这里处理我们在定义位置时使用的各种pattern,需要ResourcePatternResolver. try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); //载入资源 int loadCount =loadBeanDefinitions(resources); if (actualResources!= null) { for (int i = 0; i <resources.length; i++) { actualResources.add(resources[i]); } } return loadCount; } } else { // 这里通过ResourceLoader来完成位置定位,一个位置定义转化为Resouce接口后可以供XmlBeanDefinitionReader来使用了 Resource resource = resourceLoader.getResource(location); //这里就是解析配置开始了!!! int loadCount =loadBeanDefinitions(resource); if (actualResources!= null) { actualResources.add(resource); } return loadCount; } }
这里是策略模式中,强制使用了ResouceLoader的实现类。根据web.xml中配置的字符串,来实现不同的resouce:如果是classpath开头,则实现classpathResouce,如果不是话,就尝试UrlResouce,再不然就是filesystemResouce
//【代码清单】:DefaultResourceLoader.getResource(location) public Resource getResource(String location) { //如果路径以classPath:开头,即类路径 if(location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // 如果是URL方式,使用UrlResource作为bean资源对象 URLurl = new URL(location); return new UrlResource(url); } catch(MalformedURLException ex) { //如果都不是,那我们只能委托给子类由子类来决定使用什么样的资源对象了。如FiltSystemXmlApplication提供FileSystemResouce来完成从文件系统得到配置文件资源定义 return getResourceByPath(location); } } }
//【代码清单】:loadBeanDefinitions(resource) public int loadBeanDefinitions(EncodedResourceencodedResource) throws BeanDefinitionStoreException { try { //这里通过Resource得到InputStream的IO流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { //从InputStream中得到XML的解析源 InputSourceinput Source = new InputSource(inputStream); if(encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //具体的解析和注册过程 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { //关闭从Resouce中得到的IO流 inputStream.close(); } } }
当得到一个resouce对象之后,通过重载loadBeanDefinitions(Resouce resouce)开始解析配置文件.通过resouce对象,获得配置文件袋io流,然后通过io流获得xml的解析源.获得xml的解析源之后,就能获得xml的document对象了
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { int validationMode = getValidationModeForResource(resource); //通过xml源获得document对象 Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); return registerBeanDefinitions(doc, resource); } //异常信息 }
获得了document对象,接下来就是我们熟悉的dom解析了,下回合再详解。
三、总结
这篇博文主要介绍了spring解析配置文件的主要流程:先创建一个容器(beanFactory),然后创建一个解析器(reader),获取配置文件(resouce)后,通过这个解析器解析这个配置文件(document)。
本文出自 “总有贱人想害朕。” 博客,请务必保留此出处http://yoyanda.blog.51cto.com/9675421/1717854
原文:http://yoyanda.blog.51cto.com/9675421/1717854