读完这篇文章你将会收获到node
咱们先从一个简单常见的代码入手分析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 代码主要作了面试
因此整体而言就是资源的加载、加载、注册三个步骤spring
对于资源的加载能够看看我另外一篇文章 Spring-资源加载(源码分析)微信
加载的过程则是将 Resource 对象转为一系列的 BeanDefinition 对象app
注册则是将 BeanDefinition 注入到 BeanDefinitionRegistry 中编辑器
在分析源码流程以前咱们一块儿先对一些重要的组件混个眼熟ide
defaultListableBeanFactory 是整个 bean 加载的核心部分,是 bean 注册及加载 bean 的默认实现函数
对于 AliasRegistry 能够参考我另外一篇文章 Spring-AliasRegistry 。关于这个类咱们只要记住两点,一个是它是一个 beanFactory、一个是它是一个 BeanDefinitionRegistry源码分析
从 XML 资源文件中读取并转换为 BeanDefinition 的各个功能
对 Resource 文件进行转换、将 Resource 文件转换为 Document 文件
读取 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 {
// 从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
是否还有元素了,若是没有则直接调用 ThreadLocal
的 remove
方法。这个就是 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); } } } 复制代码
这个方法的做用很简单、就是使用一开始咱们传给 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<>(); // 判断是否配置了 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); } 复制代码
大体的流程就是如此了,面试的时候大体说出 XmlBeanDefinitionReader,DocumentLoader,BeanDefinitionDocumentReader,BeanDefinitionRegistry,AliasRegistry
这几个组件,面试官大几率会认为你是真的看过 Spring 的这部分代码的
往期相关文章
本文使用 mdnice 排版