读完这篇文章你将会收获到
引言
我们先从一个简单常见的代码入手分析
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean class="com.demo.data.Person">
<description>
微信搜一搜:CoderLi(不妨关注➕一下?这次一定?)
</description>
</bean>
</beans>
public static void main(String[] args) {
Resource resource = new ClassPathResource("coderLi.xml");
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(resource);
}
上面这段 Java 代码主要做了
所以总体而言就是资源的加载、加载、注册三个步骤
组件介绍
在分析源码流程之前我们一起先对一些重要的组件混个眼熟
DefaultListableBeanFactory
defaultListableBeanFactory 是整个 bean 加载的核心部分,是 bean 注册及加载 bean 的默认实现
对于 AliasRegistry 可以参考我另一篇文章 Spring-AliasRegistry 。关于这个类我们只要记住两点,一个是它是一个 beanFactory、一个是它是一个 BeanDefinitionRegistry
XmlBeanDefinitionReader
从 XML 资源文件中读取并转换为 BeanDefinition 的各个功能
DocumentLoader
对 Resource 文件进行转换、将 Resource 文件转换为 Document 文件
BeanDefinitionDocumentReader
读取 Document 并向 BeanDefinitionRegistry 注册
源码分析
loadBeanDefinitions(Resource)
``XmlBeanDefinitionReader#loadBeanDefinitions(Resource)
`` 我们先从这个入口方法开始进去
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
``EncodedResource
`` 是 Resource 的子类, Spring-资源加载(源码分析)
public class EncodedResource implements InputStreamSource {
private final Resource resource;
@Nullable
private final String encoding;
@Nullable
private final Charset charset;
..........
..........
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());
}
}
}
只是一个简单的 Wrapper 类,针对不同的字符集和字符编码返回不一样的 Reader
loadBeanDefinitions(EncodedResource)
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException("");
}
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("");
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
首先从 ``ThreadLocal
` 中获取正在加载的
`Resource
`,这里主要是检查
`import
`` 标签带来的循环引用问题。
从这里我们可以看到在 ``finally
` 中,对已经完成加载的资源进行移除,并且检查
`Set
` 是否还有元素了,如果没有则直接调用
`ThreadLocal
` 的
`remove
` 方法。这个就是 ThreadLocal 的最佳实践了,最后的
`remove
` 方法的调用可以避免 ThreadLocal 在 ThreadLocalMap 中作为
`WeakReference
`` 而带来的内存泄露问题。
这个方法里基本做啥事情、最主要的事情就是调用了 ``doLoadBeanDefinitions
` 这个方法,而这个方法才是真正干活的。(在 Spring 中,很有意思的是、真正干活的方法前缀都是带有
`do
`` 的,这个可以留意下)
doLoadBeanDefinitions(InputSource Resource)
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
return count;
``doLoadDocument
` 这个方法就是将 Resource 转化为 Document,这里涉及到 xml 文件到验证,建立对应的 Document Node ,使用到的就是上面提及到的
`DocumentLoader
`` 。这个不展开来探讨。
我们直接进入到 ``registerBeanDefinitions
`` 方法中
registerBeanDefinitions(Document,Resource)
public int registerBeanDefinitions(Document doc, Resource resource) {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
上面代码中出现了一个我们提及到的 ``BeanDefinitionDocumentReader
`` 组件,他的功能就是读取 Document 并向 BeanDefinitionRegistry 注册
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
这里又来了、``do
`` 才是真正干活的大哥
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
这里主要的方法就是 ``parseBeanDefinitions
``
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);
}
}
Spring 的默认标签有 ``import
` ,
`beans
` ,
`bean
` ,
`alias
``
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
doRegisterBeanDefinitions(ele);
}
}
解释 ``import
` 标签调用
`importBeanDefinitionResource
` 最终会调用到我们最开始处理
`Resource
` 循环依赖的那个方法
`loadBeanDefinitions
`` 中
我们直接进入到 ``processAliasRegistration
`` 方法中
protected void processAliasRegistration(Element ele) {
String name = ele.getAttribute(NAME_ATTRIBUTE);
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
最重要的一行代码就是将 name 和 alias 进行注册(这里注册的是 alias 标签中的 name 和 alias 之间的关系),可以参考这篇文章进行了解 Spring-AliasRegistry
我们来到最主要的 ``processBeanDefinition
``
protected void processBeanDefinition(Element ele,
BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException ex) {
.....
}
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
我们先分析 ``BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())
`` 这句代码
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
这个方法的作用很简单、就是使用一开始我们传给 ``XmlBeanDefinitionReader
` 的
`BeanDefinitionRegistry
`` 对 bean 和 beanDefinition 的关系进行注册。并且也对 beanName 和 alias 的关系进行注册(这里是对 bean 标签中配置的 id 和 name 属性关系进行配置)
``delegate.parseBeanDefinitionElement(ele)
`` 我们再把眼光返回到这个方法、这个方法就是创建 BeanDefinition 的地方了
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(...);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
} else {
beanName = this.readerContext.generateBeanName(beanDefinition);
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
} catch (Exception ex) {
.........
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
在 bean 标签中 name 属性对应的就是 alias ,id 属性对应的就是 beanName 了
当我们没有配置 id 属性但是配置了 name 属性、那么第一个 name 属性就会成为我们的 id
当我们既没有配置 id 属性 也没有配置 name 属性,那么 Spring 就会帮我们生成具体可看看 Spring-AliasRegistry
然后就创建了一个 BeanDefinitionHolder 返回了
上面的代码我们看到有这个关键的方法 ``parseBeanDefinitionElement(ele, beanName, containingBean)
`` 这个方法生成了我们期待的 BeanDefinition ,但是里面的内容都是比较枯燥的
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
都是去解析 bean 标签里面的各种属性
那么我们整个 Spring 容器初始化流程就介绍完了
总结
public static void main(String[] args) {
Resource resource = new ClassPathResource("coderLi.xml");
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(resource);
}
调用 XmlBeanDefinitionReader 的方法 loadBeanDefinitions
将 Resource 包裹成 EncodeResource
通过 ThreadLocal 判断是否 Resource 循环依赖
使用 DocumentLoader 将 Resource 转换为 Document
使用 BeanDefinitionDocumentReader 解释 Document 的标签
解释 Spring 提供的默认标签/自定义的标签解释
- 解释 import 标签的时候会回调到步骤2中
- 解释 alias 标签会向 AliasRegistry 注册
- 解释 bean 标签会向 BeanDefinitionRegistry 注册 beanName 和 BeanDefinition ,也会注册 bean 标签里面 id 和 name 的关系(其实就是 alias )
大致的流程就是如此了,面试的时候大致说出 ``XmlBeanDefinitionReader,DocumentLoader,BeanDefinitionDocumentReader,BeanDefinitionRegistry,AliasRegistry
`` 这几个组件,面试官大概率会认为你是真的看过 Spring 的这部分代码的
往期相关文章
Spring-资源加载(源码分析)
Spring-AliasRegistry
编译Spring5.2.0源码
评论