本文已收录【修炼内功】跃迁之路
距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能有新的突破!