Spring 容器的初始化

读完这篇文章你将会收获到node

  • 了解到 Spring 容器初始化流程
  • ThreadLocal 在 Spring 中的最佳实践
  • 面试中回答 Spring 容器初始化流程

引言

咱们先从一个简单常见的代码入手分析web

<?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 代码主要作了面试

  • 资源的获取(定位)
  • 建立一个 beanFactory
  • 根据 beanFactory (实现了 BeanDefinitionRegistry 接口) 建立一个 beanDefinitionReader
  • 装载资源并 registry 资源里面的 beanDefinition

因此整体而言就是资源的加载、加载、注册三个步骤spring

  • 对于资源的加载能够看看我另外一篇文章 Spring-资源加载(源码分析)微信

  • 加载的过程则是将 Resource 对象转为一系列的 BeanDefinition 对象app

  • 注册则是将 BeanDefinition 注入到 BeanDefinitionRegistry 中编辑器

组件介绍

在分析源码流程以前咱们一块儿先对一些重要的组件混个眼熟ide

DefaultListableBeanFactory

defaultListableBeanFactory 是整个 bean 加载的核心部分,是 bean 注册及加载 bean 的默认实现函数

defaultListableBeanFactory 类图
defaultListableBeanFactory 类图

对于 AliasRegistry 能够参考我另外一篇文章 Spring-AliasRegistry 。关于这个类咱们只要记住两点,一个是它是一个 beanFactory、一个是它是一个 BeanDefinitionRegistry源码分析

XmlBeanDefinitionReader

从 XML 资源文件中读取并转换为 BeanDefinition 的各个功能

XmlBeanDefinitionReader 类图
XmlBeanDefinitionReader 类图

DocumentLoader

对 Resource 文件进行转换、将 Resource 文件转换为 Document 文件

DocumentLoader 类图
DocumentLoader 类图

BeanDefinitionDocumentReader

读取 Document 并向 BeanDefinitionRegistry 注册

BeanDefinitionDocumentReader
BeanDefinitionDocumentReader

源码分析

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 {
  // 从Thread Local 中获取正在加载的的资源  Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();   // 判断这个资源是否已经加载过了、主要是为了是不是 资源的循环依赖 import  if (!currentResources.add(encodedResource)) {  throw new BeanDefinitionStoreException("");  }   try (InputStream inputStream = encodedResource.getResource().getInputStream()) {  InputSource inputSource = new InputSource(inputStream);  // 有encode 就设置进去  if (encodedResource.getEncoding() != null) {  inputSource.setEncoding(encodedResource.getEncoding());  }  // 真正的加载  return doLoadBeanDefinitions(inputSource, encodedResource.getResource());  }  catch (IOException ex) {  throw new BeanDefinitionStoreException("");  }  finally {  // ThreadLocal的最佳实践  currentResources.remove(encodedResource);  if (currentResources.isEmpty()) {  this.resourcesCurrentlyBeingLoaded.remove();  }  }  } 复制代码

首先从 ThreadLocal 中获取正在加载的 Resource,这里主要是检查 import 标签带来的循环引用问题。

从这里咱们能够看到在 finally 中,对已经完成加载的资源进行移除,而且检查 Set 是否还有元素了,若是没有则直接调用 ThreadLocalremove 方法。这个就是 ThreadLocal 的最佳实践了,最后的 remove 方法的调用能够避免 ThreadLocal 在 ThreadLocalMap 中做为 WeakReference 而带来的内存泄露问题。

这个方法里基本作啥事情、最主要的事情就是调用了 doLoadBeanDefinitions 这个方法,而这个方法才是真正干活的。(在 Spring 中,颇有意思的是、真正干活的方法前缀都是带有 do 的,这个能够留意下)

doLoadBeanDefinitions(InputSource Resource)

// 获取 document 对象
Document doc = doLoadDocument(inputSource, resource); // 注册 bean definition 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) {
 // 建立一个 bean definition 的 reader  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();  // 注册以前已经有的 bean definition 的个数 return this.beanDefinitionMap.size();  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)) {  // 处理 profiles  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;  }  }  }  // 解释前的处理 这里默认为空实现、子类能够覆盖此方法在解释 Element 以前作些事情  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)) {  // spring 默认标签解释  parseDefaultElement(ele, delegate);  } else {  // 自定义 标签解释  delegate.parseCustomElement(ele);  }  }  }  } else {  delegate.parseCustomElement(root);  } } 复制代码

Spring 的默认标签有 import , beans , bean , alias

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
 // 解释 import 标签  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   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 {   // 注册 bean Name  String beanName = definitionHolder.getBeanName();  registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());   // 注册 alias .  String[] aliases = definitionHolder.getAliases();  if (aliases != null) {  for (String alias : aliases) {  registry.registerAlias(beanName, alias);  }  }  } 复制代码

这个方法的做用很简单、就是使用一开始咱们传给 XmlBeanDefinitionReaderBeanDefinitionRegistry 对 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<>();  // 判断是否配置了 name 属性、对name 进行分割   // 在 bean 标签中 name 就是 alias 了  if (StringUtils.hasLength(nameAttr)) {  String[] nameArr = StringUtils.tokenizeToStringArray(...);  aliases.addAll(Arrays.asList(nameArr));  }   String beanName = id;  // 没有配置id 而且 alias 列表不为空、则选取第一个 alias 为 bean Name  if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {  beanName = aliases.remove(0);  }   if (containingBean == null) {  // 检查 beanName 和alias 的惟一性  checkNameUniqueness(beanName, aliases, ele);  }   // 怎么生成一个BeanDefinition 尼  AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);   if (beanDefinition != null) {  // 若是 beanName 为 空  if (!StringUtils.hasText(beanName)) {  try {  if (containingBean != null) {  beanName = BeanDefinitionReaderUtils.generateBeanName(  beanDefinition, this.readerContext.getRegistry(), true);  } else {  // 没有配置 beanName 和 alias的话、那么这个类的第一个实例、将拥有 全类名的alias  // org.springframework.beans.testfixture.beans.TestBean 这个是别名(TestBean#0 才拥有这个别名、其余的不配拥有)  // org.springframework.beans.testfixture.beans.TestBean#0 这个是 beanName  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);  }  // nothing  return null; } 复制代码

在 bean 标签中 name 属性对应的就是 alias ,id 属性对应的就是 beanName 了

当咱们没有配置 id 属性可是配置了 name 属性、那么第一个 name 属性就会成为咱们的 id

当咱们既没有配置 id 属性 也没有配置 name 属性,那么 Spring 就会帮咱们生成具体可看看 Spring-AliasRegistry

而后就建立了一个 BeanDefinitionHolder 返回了

上面的代码咱们看到有这个关键的方法 parseBeanDefinitionElement(ele, beanName, containingBean) 这个方法生成了咱们期待的 BeanDefinition ,可是里面的内容都是比较枯燥的

// 解释class 属性
String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) {  className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } // 是否指定了 parent bean String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) {  parent = ele.getAttribute(PARENT_ATTRIBUTE); }  // 建立 GenericBeanDefinition  AbstractBeanDefinition bd = createBeanDefinition(className, parent);  // 解释各类默认的属性  parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);  // 提取describe  bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));  // 解释元数据  parseMetaElements(ele, bd);  // look up 方法  parseLookupOverrideSubElements(ele, bd.getMethodOverrides());  // replacer  parseReplacedMethodSubElements(ele, bd.getMethodOverrides());  // 解析构造函数参数  parseConstructorArgElements(ele, bd);  // 解释property子元素  parsePropertyElements(ele, bd);  // 解释qualifier  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);  } 复制代码
  1. 调用 XmlBeanDefinitionReader 的方法 loadBeanDefinitions
  2. 将 Resource 包裹成 EncodeResource
  3. 经过 ThreadLocal 判断是否 Resource 循环依赖
  4. 使用 DocumentLoader 将 Resource 转换为 Document
  5. 使用 BeanDefinitionDocumentReader 解释 Document 的标签
  6. 解释 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源码

此次必定?
此次必定?

本文使用 mdnice 排版

相关文章
相关标签/搜索