本文共 12858 字,大约阅读时间需要 42 分钟。
容器的基本用法
一般来说实例化一个Bean的配置基本是这样子的的注意:这里只贴出了几个主要的类,还有他的接口实现没有贴出来
上面这些代码其实给我们完成了以下几种功能
beans包的层级结构
核心类介绍1. DefaultListableBeanFactory
(package org.springframework.beans.factory.support) DefaultListableBeanFactory是整个Bean加载的核心部分,是Spring注册和加载bean的核心实现,DefaultListableBeanFactory继承AbstractAutowireCapableBeanFactory 并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口,我们可以从iead导出他的类结构图,如下 下面我对每一个类做一个大致的介绍: AliasRegistry:定义对alias的简单的增删改查等等操作 SimpleAliasRegistry: 主要使用map做为alias缓存,并且对接口AliasRegistry进行实现 SingletonBeanRegistry:定义对单例的注册以及获取 BeanFactory:定义获取Bean以及各种Bean的属性 DefaultSingletonBeanRegisty:对接口SingletonBeanRegistry各个函数的实现 HierarchicalBeanFactory:继承了BeanFactory,也就是在BeanFactory定义的功能上面增加了对parentFactory的支持 BeanDefinitionRegistry:定义了对BeanDefiniton的各种增删改操作 FactoryBeanRegistrySupport:在DefaultSingleBeanRegister基础上面增加了对FactoryBean的特殊处理 configurableBeanFactory:提供配置Factory的各种方法 ListableBeanFactory:根据各种条件获取bean的配置清单 AbstarctBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能 AutowireCapableBeanFactory:提供了创建bean,自动注入,初始化,以及应用bean的后处理器 AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现 configurableListableBeanFactory:BeanFactory配置清单,指定忽略类型以及接口等等 DefaultListableBeanFactory:综上述功能,主要是对Bean注册后的处理2. XmlBeanDefinitionReader
(package org.springframework.beans.factory.xml) xml配置文件的读取是Spring的重要功能,S大部分是以配置为切入点的ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
BeanDefinitionReader:主要定义资源文件的读取并转换为BeanDefinition的各个功能 Environmentcapable:定义获取Environment方法 DocumnetLoader:定义从资源文件加载到转换为Document的功能 AbstractBeanDefinitionReader:对EnvironmentCapable,BeanDefinitionReader类定义功能的实现 BeanDefinitionParserDelegate:定义解析Element的方法 由此我们可以推断出spring读取xml配置文件的流程(一)通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转为对应的Resource文件
(二)通过DocumentLoader对Resource文件解析转换,将Resource文件转换为Documnet文件
(三)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParseDelegate对Element进行解析
注意: 我构建的spring5的环境,这里xmlBeanFactory 在spring3.0就已经弃用所以不能使用
BeanFactory ctx = new XmlBeanFactory(new ClassPathResource(“spring.xml”)); 但是我们可以用 ClassPathXmlApplication读取相关的Bean 最后我是基于Spring源码深度解析这本书来学校spring源码的,所以在这里还是研究一下xmlBeanFacctory,后面在介绍其他替代方法
首先看一下时序图
我们可以看到在测试类中首先调用了classPathResource的构造函数来构造Resource资源文件的实例对象,然后后续资源处理就可以用Resource提供的各种服务来操作,但是Resource资源是如何进行封装的配置文件的封装
Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource(“spring.xml”) 在java中,将不同的来源的资源抽象为一个url,通过注册不同的handler来处理不同来源的资源读取逻辑,一般的handler的类型使用不同的前缀来表示, Spring对于资源的操作(检查当前资源是否存在,当前资源是否可读。。。)提供了Resource接口 抽象了所有的Spring内部所用到的底层资源,File,URL,ClassPath等等,首先定义了3个判断当前资源状态的方法**,存在性,可读性,是否处于打开状态**,另外Resource还提供了不同资源到URL,URI类型的转换,已经获取lastModified属性,文件名,为了方便操作Resource还提供了基于当前资源创建的一个相对方法,createRelative(),getDescription()方法在错误处处理打印信息 由此图可以看出当调用super父类函数进行初始化之后,就开始调用xmlBeanDefinitionReader进行资源加载了,我们首先看他的构造函数 我们一直跟踪代码从(XmlBeanFactory →DefaultListableBeanFactory→AbstractAutowireCapableBeanFactory)到AbstractAutowireCapableBeanFactory中,发现最后调用的是这样一段代码其中里面的忽略给的接口的自动装配功能大体来说是这样子的的
当A有属性B,那么在当Spring在获取A的Bean的时候如果他的属性还没有初始化,那么Spring会自动初始化,但是也有情况,B不能被初始化,比如B实现了BeanNameAware接口,Spring是这样子介绍的,自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者通过ApplicationContext通过ApplicationContextAware进行注入
我们看一下ignoreDependencyInterface方法,首先他分别传入了几个不同的(接口)
1 BeanNameAware
如果某个bean需要访问配置文件中本身bean的id属性,这个bean类通过实现BeanNameAware接口,在依赖关系确定之后,初始化方法之前,提供回调自身的能力,从而获得本身bean的id属性,该接口提供了 void setBeanName(String name)方法,需要指出的时该方法的name参数就是该bean的id属性。回调该setBeanName方法可以让bean获取自身的id属性2.BeanFactoryAware
实现了BeanFactoryAware接口的bean,可以直接通过beanfactory来访问spring的容器,当该bean被容器创建之后,会有一个相应的beanfactory的实例引用。该 接口有一个方法void setBeanFactory(BeanFactory beanFactory)方法通过这个方法的参数创建它的BeanFactory实例,实现了BeanFactoryAware接口,就可以让Bean拥有访问Spring容器的能力3.BeanClassLoaderAware
spring会把加载业务bean类时使用的类加载器暴露出来加载Bean
既然*this.reader.loadBeanDefinitions(resource)*是资源最终加载的代码,那么我们来看一下他的时序图 从图中可以看出,整个处理过程无非就是以下几个X m l B e a n D e f i n i t i o n R e a d e r \color{red}{XmlBeanDefinitionReader} XmlBeanDefinitionReader
(XmlBeanDefinitionReader.java)@Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
👇
(XmlBeanDefinitionReader.java)public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isTraceEnabled()) { logger.trace("Loading XML bean definitions from " + encodedResource); } // resourcesCurrentlyBeingLoaded是一个ThreadLocal,里面存放Resource包装类的set集合 SetcurrentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } //如果set中已有这个元素则返回false,进入该条件抛出异常 if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { // 注意InputSource并不来自于spring 它来自于org.xml.sax InputSource inputSource = new InputSource(inputStream); // 设置编码 if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 真正加载资源文件的方法 1 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { // 资源加载完毕,移除该Resource currentResources.remove(encodedResource); // 如果集合为空,移除 if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
我们继续进入到doLoadBeanDefinitions之中
(XmlBeanDefinitionReader.java)protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //根据不同的xml约束(dtd,xsd等),将xml文件生成对应的文档对象 //1.这个方法里面涉及xml的解析 Document doc = doLoadDocument(inputSource, resource); //注册BeanDefinitions int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
doLoadBeanDefinitions分为三个部分即
下面我们一步一步的来
首先我们应该xml文件的验证保证了xml的准确性,而xml文件验证比较常用的模式有两种DTD和XSD
DTD和XSD的区别
DTD(Documnet Type Definition)属于文档类型定义,是一种XML约束模式语言,是XML文件的一种验证机制,属于XML文件的组成部分 DTD文档包含:元素的定义,元素间关系的定义规则,元素可使用属性,可使用的实体或者是符合规则 使用DTD需要在头部声明,如果Spring中使用DTD, 不过 现 在 基 本 已 被 X S D 文 档 取 代 \color{red}{现在基本已被XSD文档取代} 现在基本已被XSD文档取代XSD
XML Schema语言就是XSD(XML Schemas Definition),它描述了XML文档的结构,可以用一个指定的XML Schema来验证某一个XML文档,用来检查这个XML文档是否符合规范 在使用XML Schema文档对XML实例进行检验时,除了要声明命名空间外,还必须指定这个命名空间对应的XML Schema文档的存储位置 通schemaLoaction属性指定命名空间对应的xml Schema文件包含两个部分: (一)名称空间的URL (二)该命名空间所包含的XML Schema的文件位置或URL地址上面我们简单的介绍了一下xsd和dtd,下面我们看一下Spring的验证模式是怎么读取的
进入doLoadDocument 继续点击getValidationModeForResource 这里的方法调用顺序为(都在XmlBeanDefinitionReader这个类里面调用) l o a d B e a n D e f i n i t i o n s → d o L o a d B e a n D e f i n i t i o n s → d o L o a d D o c u m e n t → g e t V a l i d a t i o n M o d e F o r R e s o u r c e \color{red}{loadBeanDefinitions→doLoadBeanDefinitions→doLoadDocument→getValidationModeForResource} loadBeanDefinitions→doLoadBeanDefinitions→doLoadDocument→getValidationModeForResource(XmlBeanDefinitionReader.java) /* * 通过给定Resource给出验证模式。如果没有明确配置验证模式,那么调用detectValidationMode方法去 检测,可以通过xmlBeanDefintionReader.setValidationMode来进行设定 */ protected int getValidationModeForResource(Resource resource) { // 默认自动验证 1 int validationModeToUse = getValidationMode(); // 如果手动指定验证模式则使用指定的验证模式 if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } //如果有给出具体验证方式,则使用自动检测返回结果 int detectedMode = detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; } // 如果实在不能判断验证模式是那种就使用XSD方式, // 因为检测完后还是没有发现DTD模式的声明(在查找document的根标签之前)。 // 值为3 return VALIDATION_XSD; }
因为自动检测模式是在函数detectValidationMode方法里面实现的,我们进入detectValidationMode
`(XmlBeanDefinitionReader.java) //检测执行xml文件时该用哪种验证方式,这个xml由Resource对象提供 // 如果这个文件有DOCTYPE声明,那么就用DTD验证,否则就假定使用XSD。 // 如果你想要自定义自动验证模式的解决方式,你可以覆盖这个方法 protected int detectValidationMode(Resource resource) { // 默认false if (resource.isOpen()) { throw new BeanDefinitionStoreException( "Passed-in Resource [" + resource + "] contains an open stream: " + "cannot determine validation mode automatically. Either pass in a Resource " + "that is able to create fresh streams, or explicitly specify the validationMode " + "on your XmlBeanDefinitionReader instance."); } InputStream inputStream; try { inputStream = resource.getInputStream(); } catch (IOException ex) { throw new BeanDefinitionStoreException( "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + "Did you attempt to load directly from a SAX InputSource without specifying the " + "validationMode on your XmlBeanDefinitionReader instance?", ex); } try { return this.validationModeDetector.detectValidationMode(inputStream); } catch (IOException ex) { throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", ex); } }由上面的代码可以看出来xmlBeanDefintionReader的detectValidationMode函数又调用了XmlValidationModeDetector的detectValidationMode 这里的方法调用顺序为(ps:[…]这里理解为上面的加上上面的调用是他的整体调用流程) [ . . . ] → d e t e c t V a l i d a t i o n M o d e ( 类 : X m l B e a n D e f i n i t i o n R e a d e r ) → d e t e c t V a l i d a t i o n M o d e ( 类 : X m l V a l i d a t i o n M o d e D e t e c t o r ) \color{red}{[...]→detectValidationMode(类:XmlBeanDefinitionReader)→detectValidationMode(类:XmlValidationModeDetector)} [...]→detectValidationMode(类:XmlBeanDefinitionReader)→detectValidationMode(类:XmlValidationModeDetector)
( XmlValidationModeDetector.java) public int detectValidationMode(InputStream inputStream) throws IOException { // 查看文件以寻找DOCTYPE BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { boolean isDtdValidated = false; String content; while ((content = reader.readLine()) != null) { //消耗注释 content = consumeCommentTokens(content); //剥离注释后完全没内容就继续循环 if (this.inComment || !StringUtils.hasText(content)) { continue; } //有DOCTYPE声明,就跳出去 if (hasDoctype(content)) { isDtdValidated = true; break; } //注释不能进去。开头是"<",后面第一个字符是字母,就进入。 //比如'"还没有DOCTYPE声明, //那么就判定他为XSD验证 return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD); } catch (CharConversionException ex) { // Choked on some character encoding... // Leave the decision up to the caller. return VALIDATION_AUTO; } finally { reader.close(); } }
我们可以看到consumeCommentTokens
@Nullable private String consumeCommentTokens(String line) { // 首先如果没有带 的直接返回内容代表没有再注释里面 int indexOfStartComment = line.indexOf(START_COMMENT); if (indexOfStartComment == -1 && !line.contains(END_COMMENT)) { return line; } String result = ""; String currLine = line; if (indexOfStartComment >= 0) { result = line.substring(0, indexOfStartComment); currLine = line.substring(indexOfStartComment); } while ((currLine = consume(currLine)) != null) { //没有在注释中或者不是由注释开头的返回内容,inComment的这个标识位表示当前是否在注释中 if (!this.inComment && !currLine.trim().startsWith(START_COMMENT)) { return result + currLine; } } return null; }
至此xmL的验证已经完成,下一章我继续总结Spring是如何获取Domcument的
转载地址:http://nnkgn.baihongyu.com/