开篇
本文次要基于SpringFramework5.2.0.RELEASE
版本,源码的下载步骤在别的文章中曾经讲过,这里就不再赘述。
容器的根本用法
咱们先创立一个简略的示例来看一下容器的根本用法。
创立一个简略的 Java Bean。
/** * @author 神秘杰克 * 公众号: Java菜鸟程序员 * @date 2022/3/15 * @Description 简略的bean实例 */public class MyTestBean { private String testStr = "testStr"; public String getTestStr() { return testStr; } public void setTestStr(String testStr) { this.testStr = testStr; }}
创立一个简略 Spring 配置文件。
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myTestBean" class="cn.jack.MyTestBean"/></beans>
ok,编写一个测试类进行测试。
/** * @author 神秘杰克 * 公众号: Java菜鸟程序员 * @date 2022/3/15 * @Description 测试类 */@SuppressWarnings("deprecation")public class BeanFactoryTest { @Test public void testSimpleLoad(){ final BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml")); final MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean"); assertEquals("testStr",myTestBean.getTestStr()); }}
运行之后能够看到执行胜利,示例就这么简略。
平时咱们并不会间接应用 BeanFactory,而是应用 ApplicationContext,这里只是为了之后能够更好地剖析 Spring 原理。
功能分析
这部分代码实现的性能如下:
- 读取配置文件 beanFactoryTest.xml
- 依据配置文件中的配置找到对应类的配置,并实例化
- 调用实例化后的实例
依照这些形容,咱们能够理解须要至多三个类来实现。
ConfigReader
:用于读取及验证配置文件,随后放到内存中ReflectionUtil
:依据配置文件内容,进行反射实例化,也就是咱们配置文件中的<bean id="myTestBean" class="cn.jack.MyTestBean"/>
App
:用于实现整个逻辑的串联
依照原始的思维形式,整个过程就是这样,然而这么优良的 Spring 框架的构造组成是什么样子?
Spring 的层级架构
咱们首先尝试梳理 Spring 的框架结构,用全局的角度理解 Spring 的形成。
Beans 包的层级架构
实现方才的示例,咱们次要应用的就是 org.springframework.beans.jar。
剖析源码前,咱们首先理解两个外围类。
1.DefaultListableBeanFactory
咱们方才应用的 XmlBeanFactory 继承自DefaultListableBeanFactory
,而 DefaultListableBeanFactory 是整个加载的外围局部,是 Spring 注册及加载 bean 的默认实现,而在 XmlBeanFactory 中应用了自定义的 XML 读取器XmlBeanDefinitionReader
,实现了个性化的 BeanDefinitionReader 读取。DefaultListableBeanFactory 继承了 AbstractAutoWireCapableBeanFactory 并实现了 ConfigurableListableBeanFactory 以及 BeanDefinitionRegistry 接口。
从该类图咱们能够清晰理解 DefaultListableBeanFactory 的脉络,咱们先简略理解一下各个类的作用。
AliasRegistry
:定义对 alias 的简略增删改操作SimpleAliasRegistry
:次要应用 map 作为 alias 缓存,并对接口 AliasRegistry 进行实现SingletonBeanRegistry
:定义对单例的注册和获取BeanFactory
:定义获取 bean 和 bean 的各种属性DefaultSingletonBeanRegistry
:对接口 SingletonBeanRegistry 的各个函数实现HierarchicalBeanFactory
:继承 BeanFactory,在 BeanFactory 定义的性能根底上减少了对 parentFactory 的反对BeanDefinitionRegistry
:定义对 BeanDefinition 的各种增删改操作FactoryBeanRegistrySupport
:在 DefaultSingletonBeanRegistry 根底上减少了对 FactoryBean 的非凡解决性能ConfigurableBeanFactory
:提供配置 Factory 的各种办法ListableBeanFactory
:依据各种条件获取 bean 的配置清单AbstractBeanFactory
:综合了 FactoryBeanRegistrySupport、ConfigurableBeanFactory 的性能AutowireCapableBeanFactory
:提供创立 bean、主动注入、初始化以及利用 bean 的后处理器AbstractAutowireCapableBeanFactory
:综合 AbstractBeanFactory 性能并对接口 AutowireCapableBeanFactory 进行实现ConfigurableListableBeanFactory
:BeanFactory 配置清单,指定疏忽类型及接口等DefaultListableBeanFactory
:综合下面所有性能,次要对 bean 注册后的解决
XmlBeanFactory 继承 DefaultListableBeanFactory 进行了扩大,次要用于从 XML 中读取 BeanDefinition,对于注册和获取 bean 都是应用从父类 DefaultListableBeanFactory 继承的办法中实现,与父类不同的是 XmlBeanFactory 减少了 XmlBeanDefinitionReader 类型的 reader 属性,进行对资源文件的读取和注册。
2.XmlBeanDefinitionReader
XML 配置文件的读取是 Spring 中重要的性能,咱们能够从 XmlBeanDefinitionReader 中梳理一下资源文件读取、解析及注册的大抵脉络。
咱们首先看一下各个类的性能:
ResourceLoader
:定义资源加载器,次要利用于依据给定的资源文件地址返回对应的 ResourceBeanDefinitionReader
:次要定义资源文件读取并转换为 BeanDefinition 的各个性能EnvironmentCapable
:定义获取 Environment 办法DocumentLoader
:定义从资源文件加载到转换为 Document 的性能AbstractBeanDefinitionReader
:对 EnvironmentCapable、BeanDefinitionReader 类定义的办法进行实现BeanDefinitionDocumentReader
:定义读取 Document 并注册 BeanDefinition 性能BeanDefinitionParserDelegate
:定义解析 Element 的各种办法
通过下面的剖析,咱们能够梳理出 XML 配置文件读取的大略流程。
- 通过继承自 AbstractBeanDefinitionReader 中的办法,来应用 ResourLoader 将资源文件门路转换为对应的 Resource 文件
- 通过 DocumentLoader 对 Resource 文件进行转换,将 Resource 文件转换为 Document 文件
- 通过实现接口 BeanDefinitionDocumentReader 的 DefaultBeanDefinitionDocumentReader 类对 Document 进行解析,并应用 BeanDefinitionParserDelegate 对 Element 进行解析
容器的根底 XmlBeanFactory
在有了对 Spring 容器的大抵理解后,咱们接下来剖析上面代码的实现。
final BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
通过时序图咱们能够看到首先调用了 ClassPathResource 的构造函数来结构 Resource 资源文件的实例对象,之后的资源解决就能够用 Resource 提供的服务来操作了,之后就能够进行对 XmlBeanFactory 进行初始化了,接下来咱们看一下 Resource 资源是如何封装的。
配置文件封装
首先读取文件是通过 ClassPathResource 进行封装的,比方new ClassPathResource("beanFactoryTest.xml")
,那么 ClassPathResource 实现了什么性能?
在 Java 中,将不同起源的资源形象为 URL,而后注册不同的 handler(URLStreamHandler)来解决不同资源的读取逻辑,然而 URL 没有默认定义绝对 Classpath 或 ServletContext 等资源的 handler,尽管能够通过注册本人的 URLStreamHandler 来解析特定的 URL 前缀协定,然而这须要理解 URL 的实现机制,而且 URL 也没有提供根本的办法(比方资源是否可读、是否存在等)。因此在 Spring 中对其外部应用到的资源实现了本人的形象构造,Resource 接口封装底层资源。
public interface InputStreamSource { InputStream getInputStream() throws IOException;}
public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return exists(); } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; @Nullable String getFilename(); String getDescription();}
InputStreamSource 中只有一个办法 getInputStream(),返回一个新的 InputStream 对象。
Resource 接口形象了 Spring 外部应用到的底层资源,File、URL、Classpath 等。定义了 4 个判断资源状态的办法:exists()、isReadable()、isOpen()、isFile(),另外,Resource 接口也提供了不同资源到 URL、URI、File 类型的转换。
createRelative()办法能够基于以后资源创立一个绝对资源的办法。
getDescription()办法用来在错误处理中打印信息。
大抵理解了 Spring 中将配置文件封装为 Resource 类型的实例办法后,咱们持续看 XmlBeanFactory 的初始化过程,咱们这里通过应用 Resource 实例作为结构函数参数的办法。
public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null);}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource);}
下面的办法中this.reader.loadBeanDefinitions(resource);
就是资源加载的真正实现,也是剖析的重点之一。
咱们下面的时序图 3.1 就是这里实现的。在调用到该办法之前,咱们还要调用父类结构器进行初始化。
咱们间接跟踪到父类 AbstractAutowireCapableBeanFactory 的构造函数中:
public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class);}
ignoreDependencyInterface 的次要性能是疏忽给定接口的主动拆卸性能。为什么要这样做?
比方:当 A 中有属性 B,当 Spring 在获取 A 的 bean 的时候如果 B 还没初始化,那么 Spring 会主动初始化 B,然而在某些状况下,B 不会被初始化,其中一个状况就是 B 实现了 BeanNameAware 接口。
Spring 中这样介绍的:主动拆卸时疏忽给定的依赖接口,典型利用就是通过其余形式解析 Application 上下文注册依赖,相似于 BeanFactory 通过 BeanFactoryAware 进行注入或者 ApplicationContext 通过 ApplicationContextAware 进行注入。
加载 Bean
回到方才,咱们在 XmlBeanFactory 构造函数中调用了this.reader.loadBeanDefinitions(resource);
这句代码就是整个资源加载的入口,咱们看一下这个办法的时序图。
咱们尝试梳理一下处理过程:
- 应用 EncodedResource 类对参数 Resource 资源文件进行封装
- 从 Resource 中获取对应的 InputStream,随后结构 InputSource
- 随后应用结构的 InputSource 实例和 Resource 实例持续调用 doLoadBeanDefinitions 办法
接下来看一下 loadBeanDefinitions 办法具体实现过程。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource));}
首先咱们看一下 EncodedResource 的作用是什么,通过名字能够看进去是对资源文件进行编码解决,次要逻辑就在其中的getReader()办法中,如果设置了编码属性,Spring 会应用相应的编码作为输出流的编码。
public Reader getReader() throws IOException { if (this.charset != null) { return new InputStreamReader(this.resource.getInputStream(), this.charset); } else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { return new InputStreamReader(this.resource.getInputStream()); }}
该办法结构了一个 InputStreamReader。当结构完 EncodedResource 之后,调用了 loadBeanDefinitions 重载办法。
该办法外部就是真正的数据筹备阶段了。
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汇合 Set<EncodedResource> currentResources = 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 { //从encodedResource中获取曾经封装的Resource对象并再次从Resource中获取InputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { //筹备解析xml文件,全门路为org.xml.sax.InputSource InputSource inputSource = new InputSource(inputStream); //设置编码集 if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } 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); //如果没有其余资源了,则remove if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } }}
该办法首先将传入的 Resource 参数进行封装,目标是为了思考到 Resource 可能存在编码要求的状况,其次,通过 SAX 读取 XML 文件的形式来创立 InputSource 对象,最初将参数传入到外围解决局部doLoadBeanDefinitions(inputSource,encodedResource.getResource())
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } catch (BeanDefinitionStoreException ex) { throw ex; } // ...省略catch}
该办法次要做三件事:
- 获取对 XML 文件的验证模式
- 加载 XML 文件,失去对应的 Document
- 依据返回的 Document 注册 Bean 信息
咱们一步步看,从 XML 验证模式开始。
获取 XML 的验证模式
比拟罕用的验证 XML 正确性有两种:DTD 和 XSD。
DTD(Document Type Definition)即文档类型定义,是一种 XML 束缚模式语言,是 XML 文件的验证机制。
DTD 即文档类型定义,是一种 XML 束缚模式语言,是 XML 文件的验证机制,属于 XML 文件组成的一部分。
DTD 是一种保障 XML 文档格局正确的无效办法,能够通过比拟 XML 文档和 DTD 文件来看文档是否符合规范,元素和标签应用是否正确。 一个 DTD 文档蕴含:元素的定义规定,元素间关系的定义规定,元素可应用的属性,可应用的实体或符号规定。
DTD 和 XSD 相比:DTD 是应用非 XML 语法编写的。
DTD 不可扩大,不反对命名空间,只提供十分无限的数据类型。
XSD(XML Schemas Definition),即 XML Schema 语言,针对 DTD 的缺点有 W3C 在 2001 年推出。XML Schema 自身就是一个 XML 文档,应用的是 XML 语法,因而能够很不便地解析 XSD 文档。
绝对于 DTD, XSD 具备如下劣势:
- XML Schema 基于 XML,没有专门的语法
- XML Schema 能够像其余 XML 文件一样解析和解决
- XML Schema 相比于 DTD 提供了更丰盛的数据类型
- XML Schema 提供可扩大的数据模型
- XML Schema 反对综合命名空间
- XML Schema 反对属性组
在 Spring 源码中,基于 XML 文件配置 Bean 的验证模式,个别状况下是 XSD 模式。
验证模式的读取
protected int getValidationModeForResource(Resource resource) { int validationModeToUse = getValidationMode(); //如果手动指定了验证模式则应用指定的验证模式 if (validationModeToUse != VALIDATION_AUTO) { return validationModeToUse; } // 如果未指定则应用自动检测 int detectedMode = detectValidationMode(resource); if (detectedMode != VALIDATION_AUTO) { return detectedMode; } return VALIDATION_XSD;}
Spring 检测验证模式就是通过判断是否蕴含 DOCTYPE,蕴含就是 DTD,否则是 XSD。
获取 Document
通过验证模式筹备后,就能够进行 Document 加载了,这里的 documentLoader 是一个接口,真正实现是上面的 DefaultDocumentLoader。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware());}
@Overridepublic Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { //创立文档构建器工厂对象,并初始化一些属性 //如果验证模式为XSD,那么强制反对XML名称空间,并加上schema属性 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isTraceEnabled()) { logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); //依照XML文档解析给定inputSource的内容,而后返回一个新的DOM对象 return builder.parse(inputSource);}
这段代码次要创立了一个 DocumentBuilderFactory 实例,再通过 DocumentBuilderFactory 创立了一个 DocumentBuilder,最初解析 inputSource 返回 Document 对象。
解析及注册 BeanDefinitions
咱们再回到 doLoadBeanDefinitions 办法,拿到 Document 对象后,咱们就能够注册 bean 对象,调用 registerBeanDefinitions 办法。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //实例化BeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //记录统计前BeanDefinition的加载个数 int countBefore = getRegistry().getBeanDefinitionCount(); //加载及注册bean(要害) documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //记录本次加载个数 return getRegistry().getBeanDefinitionCount() - countBefore;}
调用 registerBeanDefinitions 办法,抉择实现类为DeDefaultBeanDefinitionDocumentReader
。
@Overridepublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; //拿到了xml文档对象的根元素 并调用该办法 doRegisterBeanDefinitions(doc.getDocumentElement());}
doRegisterBeanDefinitions 开始真正地解析。
protected void doRegisterBeanDefinitions(Element root) { // 任何被嵌套的<beans>元素都会导致此办法的递归。为了正确的流传和保留<beans>的默认属性、 // 放弃以后(父)代理的跟踪,它可能为null // 为了可能回退,新的(子)代理具备父的援用,最终会重置this.delegate回到它的初始(父)援用。 // 这个行为模仿了一堆代理,但实际上并不需要一个代理 BeanDefinitionParserDelegate parent = this.delegate; //代码(1) this.delegate = createDelegate(getReaderContext(), root, parent); //默认名称空间是"http://www.springframework.org/schema/beans" if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); //在xml配置文件中对profile的设置 辨别是生产环境还是线上环境 if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } //解析前解决,留给子类实现 preProcessXml(root); //生成BeanDefinition,并注册在工厂中,代码(2) parseBeanDefinitions(root, this.delegate); //解析后处理,留给子类实现 postProcessXml(root); this.delegate = parent;}
咱们先看一下代码(1)的办法,该办法创立了一个 BeanDefinitionParserDelegate 对象,该对象是对 XML 中属性值解析的委派。
protected BeanDefinitionParserDelegate createDelegate( XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) { BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext); delegate.initDefaults(root, parentDelegate); return delegate;}
咱们看下 BeanDefinitionParserDelegate 类的常量。
public class BeanDefinitionParserDelegate { public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"; public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; "; /** * Value of a T/F attribute that represents true. * Anything else represents false. Case seNsItive. */ public static final String TRUE_VALUE = "true"; public static final String FALSE_VALUE = "false"; public static final String DEFAULT_VALUE = "default"; public static final String DESCRIPTION_ELEMENT = "description"; public static final String AUTOWIRE_NO_VALUE = "no"; public static final String AUTOWIRE_BY_NAME_VALUE = "byName"; public static final String AUTOWIRE_BY_TYPE_VALUE = "byType"; public static final String AUTOWIRE_CONSTRUCTOR_VALUE = "constructor"; public static final String AUTOWIRE_AUTODETECT_VALUE = "autodetect"; public static final String NAME_ATTRIBUTE = "name"; public static final String BEAN_ELEMENT = "bean"; public static final String META_ELEMENT = "meta"; public static final String ID_ATTRIBUTE = "id"; public static final String PARENT_ATTRIBUTE = "parent"; //...}
咱们发现 Spring 配置文件的属性全都在这里 当初咱们晓得 BeanDefinitionParserDelegate 对象的确是来对 XML 配置文件的解析后,持续回到 createDelegate 办法,创立了 BeanDefinitionParserDelegate 对象后,还执行了 initDefaults 办法,来初始化一些默认值。
解析并注册 BeanDefinition
当初咱们回到代码(2),进入 parseBeanDefinitions 办法。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //对beans的解决,默认名称空间是"http://www.springframework.org/schema/beans" if (delegate.isDefaultNamespace(root)) { //获取根元素下的子Node NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { //拿到了<beans>下的子标签 Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { //如果该标签属于beans的名称空间,则进入这个办法 //xmlns="http://www.springframework.org/schema/beans" parseDefaultElement(ele, delegate); } else { //如果该标签属于其余的名称空间比方:context,aop等 //xmlns:aop="http://www.springframework.org/schema/aop" //xmlns:context="http://www.springframework.org/schema/context" delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); }}
在 Spring 中的 XML 分为两大类,一种是默认的如下:
<bean id="test" class="cn.jack.Test"/>
另一种就是自定义的:
<tx:annotation-driven/>
如果是自定义实现的话,则须要用户实现自定义配置,如果根节点或者节点是应用默认命名的话则应用 parseDefaultElement 进行解析,否则应用 delegate.parseCustomElement 办法对自定义命名空间进行解析。
而判断是默认命名还是自定义命名空间则应用 isDefaultNamespace 办法中的 node.getNamespaceURI()获取命名空间,随后与固定的命名空间http://www.springframework.org/schema/beans
进行比照,不统一则为自定义命名空间。
对于默认标签解析与自定义标签解析则在下一篇文章中。