本文已收录【修炼内功】跃迁之路
距spring-framework开篇的那篇文章已经一个月了,如果再照这样的速度下去,这个flag估计大概率又要呵呵~
最近发生了一些事情让我迷茫于应该坚持什么,为什么还要痴迷于工作两三年本就应该掌握的东西上~
‘年’(夕兽)就要来了,总要准备点儿什么才能有资本‘除夕’不是~
言归正传,切入正题!
上一篇介绍了Spring中的 Resource
,一个类似于Linux中“一切皆文件”的概念,屏蔽了对不同类型资源操作的差异性,本篇继续深入,但一定没有各位想象或者期待中的那么深
平时接触Spring一定离不开两个基本的核心概念:容器和控制反转,然而这两个概念都不是这篇文章要展开讨论的;平时使用Spring都是从各种ApplicationContext开始(如最常见的 ClassPathXmlApplicationContext
或者SpringBoot中的 SpringApplication
),然而,ApplicationContext也不是本篇的重点~(大写调皮,后文详解)
在使用Spring的过程中,我们会(通过xml <bean class='xxx'></bean>
、注解 @Bean
、 @Component
等等方式)创建大量的 <u>bean</u> ,众所周知,bean是被 容器 所管理的,bean之间的依赖注入依靠的是 控制反转 ,那在我们使用 bean 之前,其是如何被注册到容器中的呢?
要解释这个问题其实很简单:step 1 解析;step 2 注册。本篇,我们将重心放在bean的解析上(但不包含所有方式的解析)
Spring在注册bean之前的解析方式有很多种,但无外乎三大类:1. 文件形式的配置;2. 注解形式的配置;3. 硬编码方式。本篇只谈第一类中的一种形式 -- xml配置文件的解析
为什么是xml这种千年老古董,因为市面上的书从来都只讲xml配置的解析啊(手动调皮),以上为玩笑(以下严肃脸),因为xml文件对于理解bean的解析更为容易,在注解大行其道的当下,希望我能坚持到某一天来撰写一篇文章解释注解在bean解析、注册上的运行机制
除了一些老项目(不可抗拒的历史原因)或者特殊场景依然在使用xml配置外,我相信大多数的程序猿都是向前看的,毕竟Spring(Boot)也在力推零xml配置
https://docs.spring.io/spring...
所以,我希望各位不要将重点放在如何解析xml配置上,而是将其作为“药引子”,引出其背后更为重要的概念( BeanDefinition
、 BeanFactory
、 BeanRegistry
)
基于xml配置的ApplicationContext常见的有 ClassPathXmlApplicationContext
、 FileSystemXmlApplicationContext
(见名识意),基于xml配置的BeanFactory则是 XmlBeanFactory
(ApplicationContext与BeanFactory的关系会放到后文中),而所有这些在解析bean的时候使用的都是 XmlBeanDefinitionReader
(具体如何使用会在以后的篇幅中介绍,本篇请将重点暂时放在BeanDefinitionReader上)
XmlBeanDefinitionReader
实现自 BeanDefinitionReader
,而在 BeanDefinitionReader
的实现里,除了 XmlBeanDefinitionReader
之外还有 PropertiesBeanDefinitionReader
及 GroovyBeanDefinitionReader
,一个解析properties文件、一个解析groovy脚本,由于过于小众、原理相通,此处不表(如果诸君有兴趣及能力,可以开发自己的 YamlBeanDefinitionReader 或 KotlinBeanDefinitionReader 等也未必不可)
看似复杂的一张类关系图,理解起来其实并不复杂
BeanDefinitionReader
定义了一系列bean初始化的接口,以下列出几个比较直观的
public interface BeanDefinitionReader { // 加载单个配置文件 int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; // 加载多个配置文件 int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
接口中定义了针对单个配置文件及多个配置文件的加载方法
Q:类名明明是 Reader ,但方法名为什么是 load 而不是 parse (加载和解析貌似不是一个概念)?
AbstractBeanDefinitionReader
实现了公共的处理逻辑
XmlBeanDefinitionReader
在解析的过程中主要借助了两种接口, DocumentLoader
及 BeanDefinitionDocumentReader
DocumentLoader
主要负责xml的验证和读取(默认实现为 DefaultDocumentLoader
) BeanDefinitionDocumentReader
主要负责Document的解析和注册(默认实现为 DefaultBeanDefinitionDocumentReader
)
如果觉得上图过于简单、不够细节,可以参考下图(由于Spring源码的复杂性,依然不能覆盖所有的细节)
BeanDefinition
、 BeanDefinitionHolder
) BeanDefinitionRegistry
、 AliasRegistry
) 以上,引出了几个重要的新概念( BeanDefinition
、 BeanDefinitionHolder
、 BeanFactory
、 BeanRegistry
),客官莫急,待我慢慢道来
xml的验证和读取可以跟踪到代码 XmlBeanDefinitionReader#doLoadDocument
中
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
documentLoader.loadDocument
内部实现则是常规的xml文件到Document的读取逻辑,这里简要介绍一下 EntityResolver
一般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="" class=""> <property name="" value=""/> <property name="" ref=""/> </bean> </beans>
在解析之前需要对xml的内容进行校验,校验需要依赖XSD或DTD声明文件,如上 http://www.springframework.or... (浏览器中可直接打开)
这里有一个问题,基于Spring的应用不可能全部运行在可连通互联网的环境中,如果没有网络环境,基于xml配置的Spring初始化均会失败
EntityResolver
的作用是提供一个寻找本地XSD或DTD声明文件的方法,具体的过程不再详解,可以跟到 XmlBeanDefinitionReader#getEntityResolver
查看
spring-beans的声明文件均可在spring-beans模块中找到
至此,可在离线的环境中获取XSD或DTD文件,以验证并解析xml配置文件,为后续bean的解析做足准备
Document的解析、注册主要使用了 BeanDefinitionDocumentReader
(由 DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
跟入)
在进行bean的解析之前,首先需要匹配==profiles==
Spring Profiles provide a way to segregate parts of your application configuration and make it be available only in certain environments.
简单来讲,我们可以为不同的环境设置不同的配置,有选择性的让Spring加载
xml文件中
<beans profile="dev"></beans>
SpringBoot properties文件名中
application-dev.properties
SpringBoot yaml文件中
server: port: 80 --- spring: profiles: dev server: port: 8080
注解中
@Bean @Profile({"dev"}) public MyBean myDevBean() { return new MyDevBean(); } @Bean @Profile({"production"}) public MyBean myProductionBean() { return new MyProductionBean(); }
等等方式...
java -jar xxx.jar -Dspring.profiles.active=dev
profile的配置会被读取并记录在 Environment
中,在进行bean的解析之前,第一步需要判断当前beans的profile是否与 Environment
中记录的profile相匹配,只有匹配的才会被加载,并进入解析、注册流程,这也便做到了配置的环境隔离(具体的处理过程参见 DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
)
在spring-beans模块的范畴内,默认的命名空间只包含四种标签: <beans>
、 <bean>
、 <alias>
、 <import>
,但在实际使用过程中,我们用到的远远不止这三种,比如 <tx:advice>
、 <aop:config>
等等,这些标签的命名空间并不在==beans==内,前者的命名空间为==tx==,XSD文件在 spring-tx.jar!org/springframework/transaction/config/spring-tx.xsd ,后者的命名空间为==aop==,XSD文件在 spring-tx.jar!org/springframework/aop/config/spring-aop.xsd
除了默认命名空间内的标签外,其他命名空间的标签都是以一种扩展(自定义)的形式存在
// DefaultBeanDefinitionDocumentReader.java /** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */ protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { // 默认命名空间的解析 NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // 默认命名空间(标签)的解析 parseDefaultElement(ele, delegate); } else { // 默认命名空间(标签)中,出现了自定义命名空间(标签) delegate.parseCustomElement(ele); } } } } else { // 自定义命名空间(标签)的解析 delegate.parseCustomElement(root); } }
无论默认标签还是自定义标签,实际的标签解析过程均由 BeanDefinitionParserDelegate
提供
默认命名空间的标签无外乎 <bean>
、 <alias>
、 <import>
(以及 <beans>
嵌套)
// DefualtBeanDefinitionDocumentReader.java private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 解析 <import> importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 解析 <alias> processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // 解析 <bean> processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 递归解析 <beans> 嵌套 doRegisterBeanDefinitions(ele); } }
对于 <import>
标签,解析出location,处理占位符,递归调用 loadBeanDefinitions
进行解析注册
对于 <alias>
标签,解析出bean的 name 和 alias ,并注册( AliasRegistry
)
对于 <bean>
标签,情况则会复杂很多,但也无外乎 <bean>
属性解析、 <constructor-arg>
解析、 <property>
解析等等
具体的解析细节不再赘述,大家可以自行查看源码
// BeanDefinitionParserDelegate#parseBeanDefinitionElement try { // 创建BeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); // <bean>属性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // <description> bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // <meta> parseMetaElements(ele, bd); // <lookup-method> parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // <replaced-method> parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // <constructor-arg> parseConstructorArgElements(ele, bd); // <property> parsePropertyElements(ele, bd); // <qualifier> parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; }
标签解析之后的结果存放到了那里? BeanDefinition
!
BeanDefinition
只是bean的定义,存放构造该bean实例所需要的元信息,其中包含你能想到的一切有关bean的属性信息
// BeanDefinition.java public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { // class name String getBeanClassName(); // 构造函数 ConstructorArgumentValues getConstructorArgumentValues(); // 属性 MutablePropertyValues getPropertyValues(); // 初始化的方法名 String getInitMethodName(); // 销毁的方法名 String getDestroyMethodName(); // ... }
除此之外,还有很多的辅助类,如
RuntimeBeanReference
存储 ref
信息 TypedStringValue
存储 value
信息 ManagedMap
存储 <map>
信息 ManagedList
储存 <list>
信息 至此就结束了么?当然不是,我们在配置bean的时候一般都会使用id来指定beanName,但如果没有指定id呢?
<bean id="myBean" name="aliasBean" class=""></bean>
此时,则会使用 BeanNameGenerator
(默认 DefaultBeanNameGenerator
)生成默认的beanName(参见 BeanDefinitionParserDelegate#parseBeanDefinitionElement
)
随后,会将beanDefinition、beanName及别名alias一同包装进 BeanDefinitionHolder
BeanDefinitionHolder
的定义只是简单捆绑了以上三者的关系
public class BeanDefinitionHolder implements BeanMetadataElement { // bean definition private final BeanDefinition beanDefinition; // bean name private final String beanName; // 别名alias private final String[] aliases; }
最后,将该BeanDefinition进行注册( BeanDefinitionRegistry
)
// DefaultBeanDefinitionDocumentReader.java protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 解析BeanDefinitionHolder BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { // 注册BeanDefinition BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); // 发送事件 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
Spring提供了一种能力,来扩展并自定义xml配置文件中的标签
自定义标签的实现需要依赖:
BeanDefinitionParser
实现,用来解析自定义标签 NamespaceHandlerSupport
实现,用来注册自定义标签解析器 spring.handlers
及 spring.schemas
,用来发现自定义标签注册器( NamespaceHandlerSupport
)及XSD文件 BeanDefinitionParser
的定义同样很简单,将一个Document下的Element解析为BeanDefinition(s),并注册
public interface BeanDefinitionParser { BeanDefinition parse(Element element, ParserContext parserContext); }
以 component-scan
为例,其命名空间为 context
BeanDefinitionParser
的实现类为 ComponentScanBeanDefinitionParser
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser { @Override @Nullable public BeanDefinition parse(Element element, ParserContext parserContext) { String basePackage = /* 解析basePackage */; // 扫描BeanDefinition ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); // 注册BeanDefinition registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null; } }
其中定义了如何扫描指定package下的类,并根据注解注册bean的逻辑
这里有一个有趣的发现,通过配置文件注册bean的实现类通常为xxxBeanDefinitionLoader,而通过注解注册bean的实现类通常为xxxBeanDefinitionScanner
NamespaceHandlerSupport
的实现类为 ContextNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // ... // 注册<component-scan>的解析器 registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); // ... } }
spring.handlers文件部分内容为
http/://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
spring.schemas文件部分内容为
http/://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context.xsd
同理,可以找到 <tx:advice>
、 <aop:config>
等标签的解析逻辑
回到 BeanDefinitionParserDelegate
,细心的会发现,对于自定义标签的解析逻辑,并没有出现注册的地方
// BeanDefinitionParserDelegate.java public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { // 获取命名空间 String namespaceUri = getNamespaceURI(ele); // 获取解析器 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); // 解析(Q: 并注册?) return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
对于大多数自定义标签而言,其都是自注册的,即在 BeanDefinitionParser.parse
中完成注册的逻辑,如 <component-scan>
,那对于 <tx:advice>
之类的标签是如何完成自注册的? AbstractBeanDefinitionParser
!
// AbstractBeanDefinitionParser public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser { @Override @Nullable public final BeanDefinition parse(Element element, ParserContext parserContext) { // Element解析为BeanDefinition AbstractBeanDefinition definition = parseInternal(element, parserContext); // 解析id及aliases,包装为BeanDefinitionHolder BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); // 注册!!! registerBeanDefinition(holder, parserContext.getRegistry()); // 发送事件 if (shouldFireEvents()) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } return definition; } }
AbstractBeanDefinitionParser
实现了类似于 ComponentScanBeanDefinitionParser
的逻辑,在解析出BeanDefinition后随即将其注册
上文中多次提到了注册
<alias> <bean>
注册的具体细节希望能和bean的初始化及获取放到一起来讲,本篇简单介绍并引出几个概念
bean的注册由 xxxBeanDefinitionRegistry
实现,并不是注册bean的实例对象,而是注册bean的元数据BeanDefinition
别名的注册由 xxxAliasRegistry
实现
bean的获取由 xxxBeanFactory
实现(包括bean的初始化、依赖注入等)
BeanDefinition的实现有多种,各中关系后续文章详解
xxxBeanDefinitionRegistry
与 xxxBeanFactory
的实现也是错综复杂,篇幅有限,统一放到后续文章中讲解
XmlBeanDefinitionReader
负责xml配置的bean解析及注册
DocumentLoader BeanDefinitionDocumentReader
xml中的标签分为两大类,默认标签及自定义标签,其解析逻辑不同
BeanDefinitionParserDelegate
提供统一入口 BeanDefinitionParser
、 NamespaceHandlerSupport
bean的元数据被存放在 BeanDefinition
中,并同beanName、别名alias封装在 BeanDefinitionHolder
里
xxxBeanDefinitionRegistry xxxAliasRegistry
<component-scan>
),使用注解同样可以加载基于xml配置的bean(如 @Import({"classpath:xxx.xml"})
),两者之间是如何相融合的 2019结束了,希望2020能有新的突破!