Spring IOC源码(四):IOC容器之 beanDefinition解析

1、源码解析

  配置文件的bean定义解析在obtainFreshBeanFactory()方法中完成的,核心解析是在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions()方法中完成。
 
 1 // 解析配置文件为beanDefinition,并注册到容器中
 2 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
 3    // 默认命名空间 http://www.springframework.org/schema/beans
 4    if (delegate.isDefaultNamespace(root)) {
 5        // 获取根元素<beans>的子节点
 6       NodeList nl = root.getChildNodes();
 7       for (int i = 0; i < nl.getLength(); i++) {
 8          Node node = nl.item(i);
 9          if (node instanceof Element) {
10             Element ele = (Element) node;
11             // 解析默认命名空间元素
12             if (delegate.isDefaultNamespace(ele)) {
13                parseDefaultElement(ele, delegate);
14             }
15             // 解析自定义命名空间元素
16             else {
17                delegate.parseCustomElement(ele);
18             }
19          }
20       }
21    }
22    else {
23       // 解析自定义命名空间元素
24       delegate.parseCustomElement(root);
25    }
26 }

  ·命名空间为http://www.springframework.org/schema/beans默认命名空间时,解析bean定义执行parseDefaultElement()方法;

  ·不为默认命名空间(如:http://www.springframework.org/schema/aop)时,解析bean定义执行parseCustomElement()方法。

1.1、默认元素标签解析

  DefaultBeanDefinitionDocumentReader#parseDefaultElement 解析默认标签为beanDefinition对象的核心代码:
 
 1 // 解析默认命名空间中的标签元素
 2 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
 3    // 解析import元素,并从指定的资源中加载beanDefinition至beanFactory中
 4    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
 5       importBeanDefinitionResource(ele);
 6    }
 7    // 解析alias元素
 8    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
 9       processAliasRegistration(ele);
10    }
11    // 解析bean元素
12    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
13       processBeanDefinition(ele, delegate);
14    }
15    // 解析beans元素,递归处理
16    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
17       // recurse
18       doRegisterBeanDefinitions(ele);
19    }
20 }

  parseDefaultElement()方法解析import、alias、bean、beans标签。

1、解析import标签

  DefaultBeanDefinitionDocumentReader#importBeanDefinitionResource 解析import标签的核心伪代码如下:
 
 1 // import标签的resource属性
 2 public static final String RESOURCE_ATTRIBUTE = "resource";
 3 
 4 // 解析import标签
 5 protected void importBeanDefinitionResource(Element ele) {
 6    // 获取resource属性,导入的资源文件
 7    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
 8    
 9    // 解析占位符等内容
10    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
11    // 创建存放资源路径下的Resource对象集合
12    Set<Resource> actualResources = new LinkedHashSet<>(4);
13 
14    // 判断location是绝对URI还是相对URI
15    boolean absoluteLocation = false;
16    absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
17   
18    // 如果是绝对路径URI则直接根据地质加载对应的配置文件
19    if (absoluteLocation) {
20          int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);      
21    // 如果是相对路径,根据相对路径获取资源Resource对象            
22    }else {
23       int importCount;
24       Resource relativeResource = getReaderContext().getResource().createRelative(location);
25       if (relativeResource.exists()) {
26             // 加载import的文件为beanDefinition独享
27             importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
28             actualResources.add(relativeResource);
29       }else {
30           // 如果解析不成功,则使用默认的解析器ResourcePatternResolver进行解析
31           String baseLocation = getReaderContext().getResource().getURL().toString();
32           importCount = getReaderContext().getReader().loadBeanDefinitions(
33           StringUtils.applyRelativePath(baseLocation, location), actualResources);
34      }
35    }
36    // 解析后进行监听器激活处理
37    Resource[] actResArray = actualResources.toArray(new Resource[0]);
38    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
39 }

importBeanDefinitionResource(Element ele)的核心流程:

  1、获取标签的resource - 加载资源文件的路径位置location,若导入的文件路径location为空,不做任何处理;
  2、判断资源文件位置location是绝对路径还是相对路径;
   若是绝对路径,执行加载bean定义信息的流程loadBeanDefinitions,将import的资源解析成beanDefinition并加载至beanFactory中;
   若是相对路径,从相对路径中创建资源的Resource对象,Resource对象创建成功,执行加载bean定义信息的流程loadBeanDefinitions;Resource对象创建失败,使用默认的解析器ResourcePatternResolver进行解析,执行加载bean定义信息的流程loadBeanDefinitions,将import的资源解析成beanDefinition并加载至beanFactory中;
  3、import标签处理成功后, 做监听器激活处理。

2、解析alias标签

  DefaultBeanDefinitionDocumentReader#processAliasRegistration 解析alias标签的核心伪代码:
 
 1 // bean名称属性
 2 public static final String NAME_ATTRIBUTE = "name";
 3 // bean别名属性
 4 public static final String ALIAS_ATTRIBUTE = "alias";
 5 
 6 // 处理给定的别名元素,通过注册器注册别名
 7 protected void processAliasRegistration(Element ele) {
 8    // 获取beanName
 9    String name = ele.getAttribute(NAME_ATTRIBUTE);
10    // 获取alias
11    String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
12    // 是否注册bean名称与alias别名标识
13    boolean valid = true;
14    // bean名称为空,不做处理,注册标识设置为false
15    if (!StringUtils.hasText(name)) {
16       getReaderContext().error("Name must not be empty", ele);
17       valid = false;
18    }
19    // alias别名为空,不做处理,注册标识设置为false
20    if (!StringUtils.hasText(alias)) {
21       getReaderContext().error("Alias must not be empty", ele);
22       valid = false;
23    }
24    // 注册标识为true
25    if (valid) {
26       // 注册alias
27       getReaderContext().getRegistry().registerAlias(name, alias);
28      
29       // 别名注册后通知监听器做相应处理
30       getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
31    }
32 }

  SimpleAliasRegistry#registerAlias 注册别名核心伪代码如下:

  获取标签中的bean名称、alias别名
 
 1 // 别名与bean名称的映射集合
 2 private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
 3 
 4 // alias别名注册
 5 public void registerAlias(String name, String alias) {
 6    synchronized (this.aliasMap) {
 7       // 若别名 与 bean名称相同,将别名从aliasMap缓存中移除
 8       if (alias.equals(name)) {
 9          this.aliasMap.remove(alias);
10       }else {
11          // 根据键alias获取aliasMap缓存中的bean名称
12          String registeredName = this.aliasMap.get(alias);
13          if (registeredName != null) {
14             // 若bean的别名已经被注册,不作处理
15             if (registeredName.equals(name)) {
16                return;
17             }
18             // 若该bean的别名不允许被覆盖,抛异常
19             if (!allowAliasOverriding()) {
20                throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
21                      name + "': It is already registered for name '" + registeredName + "'.");
22             }
23          }
24          // 检查别名是否循环依赖   当A->B,存在时,若再次出现A->C->B则抛出异常
25          checkForAliasCircle(name, alias);
26          // 注册bean的别名
27          this.aliasMap.put(alias, name);
28       }
29    }
30 }

processAliasRegistration(Element ele)的核心流程

  1、bean名称、alias别名的非空判断,若为空,则跳过别名解析注册;否则,别名注册流程继续;
  2、bean名称、alias别名相同,将alias别名从aliasMap缓存中移除,注册流程结束;否则,别名注册流程继续;
  3、alias别名已经注册进aliasMap中,不作处理,注册流程结束;否则,流程继续;
  4、bean的alias别名不允许被覆盖,抛出异常,注册流程结束;
  5、检查注册的别名是否存在间循环依赖,若存在,抛出异常;
  6、将alias别名作为key、bean名称作为value注册到aliasMap缓存中;
  7、别名注册成功后,通知监听器做处理。

3、解析bean标签

  DefaultBeanDefinitionDocumentReader#processBeanDefinition 解析bean标签核心伪代码如下:
 
 1 // 解析bean的定义信息
 2 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
 3    // beanDefinitionHolder是beanDefinition对象的封装类,封装了BeanDefinition,bean的名字和别名,用它来完成向IOC容器的注册
 4    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
 5    if (bdHolder != null) {
 6       bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
 7       
 8       // 向ioc容器注册解析得到的beandefinition
 9       BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
10       
11       // 在beandefinition向ioc容器注册完成之后,发送消息
12       getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
13    }
14 }

解析bean标签步骤: 

  1、解析标签,获取bean定义信息;
  2、将获取到的bean定义信息注册到容器中;
  3、完成bean定义信息的注册,通知监听器。
下面对第一步、第二步做深入了解

3.1、解析标签,获取bean定义信息 parseBeanDefinitionElement

  BeanDefinitionParserDelegate#parseBeanDefinitionElement 获取beanDefinitionHolder对象核心伪代码:
 
 1 // id标签
 2 public static final String ID_ATTRIBUTE = "id";
 3 // name标签
 4 public static final String NAME_ATTRIBUTE = "name";
 5 // 多值属性分隔符
 6 public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; ";
 7 // 读取器上下文
 8 private final XmlReaderContext readerContext;
 9 
10 // 解析bean定义信息
11 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
12    // 获取<bean>标签的属性信息id、name
13    String id = ele.getAttribute(ID_ATTRIBUTE);
14    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
15 
16    // bean别名集合的处理
17    List<String> aliases = new ArrayList<>();
18    if (StringUtils.hasLength(nameAttr)) {
19       String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
20       aliases.addAll(Arrays.asList(nameArr));
21    }
22 
23    // 默认bean标签的id,作为bean的名称
24    String beanName = id;
25    // 如果id属性未配置 并且  别名集合不为空
26    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
27       // 获取bean的alias别名
28       beanName = aliases.remove(0);
29    }
30 
31    // 校验bean名称与alias名称的唯一性,若已经被使用,抛出异常
32    if (containingBean == null) {
33       checkNameUniqueness(beanName, aliases, ele);
34    }
35 
36    // 获取bean定义信息,实际beanDefinition类型为GenericBeanDefinition
37    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
38    // bean定义信息不为空
39    if (beanDefinition != null) {
40       // 未设置beanName时,为beanDefinition生成beanName
41       if (!StringUtils.hasText(beanName)) {
42            beanName = this.readerContext.generateBeanName(beanDefinition);
43            String beanClassName = beanDefinition.getBeanClassName();
44            if (beanClassName != null &&
45                  beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
46                  !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
47               // 将beanName设置进别名容器中
48               aliases.add(beanClassName);
49            }
50       }
51       // 获取别名数组
52       String[] aliasesArray = StringUtils.toStringArray(aliases);
53       // 创建beanDefinitionHolder
54       return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
55    }
56    return null;
57 }

获取bean定义信息的流程:

  1、校验bean名称与alias别名是否唯一,若不唯一,抛出异常;
  2、创建GenericBeanDefinition的bean定义信息,包含bean的父类名称、Class对象、各属性元数据信息;
  3、如果bean名称为空,生成bean名称并初始化别名集合;
  4、创建bean定义包装对象beanDefinitionHolder,包含beanDefinition定义,bean名称、别名集合。

3.2、解析标签,获取bean定义信息 registerBeanDefinition

  BeanDefinitionReaderUtils#registerBeanDefinition 将beanDefinition注册至容器中
 
 1 // 将bean定义信息注册到IOC容器
 2 public static void registerBeanDefinition(
 3       BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
 4       throws BeanDefinitionStoreException {
 5 
 6    // 使用beanName做唯一标识注册
 7    String beanName = definitionHolder.getBeanName();
 8    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
 9 
10    // 注册所有的别名
11    String[] aliases = definitionHolder.getAliases();
12    if (aliases != null) {
13       for (String alias : aliases) {
14          registry.registerAlias(beanName, alias);
15       }
16    }
17 }

  1、通过key=beanName, value=beanDefinition的键值对将beanDefinition注册进IOC容器;

  2、注册bean的所有别名。
    beanDefinition注册的最终会进入DefaultListableBeanFactory容器的registerBeanDefinition方法,下面我们来看看核心步骤。
DefaultListableBeanFactory#registerBeanDefinition 核心伪代码:
 
 1 // bean定义对象的Map集合,beanName作为Map的Key
 2 private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
 3 // bean定义名称的集合
 4 private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
 5 
 6 // 注册bean定义信息
 7 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
 8       throws BeanDefinitionStoreException {
 9    ...
10    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
11    // 处理注册已经注册的beanName情况
12    if (existingDefinition != null) {
13       // 如果对应的beanName已经注册且在配置中配置了bean不允许被覆盖,则抛出异常
14       if (!isAllowBeanDefinitionOverriding()) {
15          throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
16       }
17       ...
18       this.beanDefinitionMap.put(beanName, beanDefinition);
19    }
20    else {
21       // IOC容器已经启动成功
22       if (hasBeanCreationStarted()) {
23          // 不能修改启动阶段的集合元素
24          synchronized (this.beanDefinitionMap) {
25             this.beanDefinitionMap.put(beanName, beanDefinition);
26             List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
27             updatedDefinitions.addAll(this.beanDefinitionNames);
28             updatedDefinitions.add(beanName);
29             this.beanDefinitionNames = updatedDefinitions;
30             removeManualSingletonName(beanName);
31          }
32       }
33       // 启动注册阶段,将bean定义信息放进启动阶段的集合元素中
34       else {
35          // 注册beanDefinition
36          this.beanDefinitionMap.put(beanName, beanDefinition);
37          // 记录beanName
38          this.beanDefinitionNames.add(beanName);
39          removeManualSingletonName(beanName);
40       }
41       this.frozenBeanDefinitionNames = null;
42    }
43 
44    if (existingDefinition != null || containsSingleton(beanName)) {
45       // 重置所有beanName对应的缓存
46       resetBeanDefinition(beanName);
47    }
48    else if (isConfigurationFrozen()) {
49       clearByTypeCache();
50    }
51 }

  bean定义信息向IOC容器注册,有个关键属性beanDefinitionMap,代表的是bean名称与bean定义信息的映射关系。

  在IOC容器的不同阶段,bean定义名称的注册略有不同,当bean定义信息注册完成,重置bean名称在注册阶段的所有缓存信息。

3.3、解析beans元素,递归处理

如果是beans标签,会通过递归的方式,调用DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions()方法,重新开始解析,此处不再赘述。

1.2、自定义元素标签解析

  BeanDefinitionParserDelegate#parseCustomElement 自定义标签解析成beanDefinition的核心步骤:
 
 1 解析自定义标签(除了默认标签外的其他标签)
 2 public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
 3    // 获取对应的命名空间
 4    String namespaceUri = getNamespaceURI(ele);
 5    if (namespaceUri == null) {
 6       return null;
 7    }
 8    // 根据命名空间找到对应的NamespaceHandler (spring.handlers)
 9    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
10    if (handler == null) {
11       error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
12       return null;
13    }
14    // 调用自定义的NamespaceHandler进行解析
15    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
16 }

  与默认的标签解析不同,自定义标签的解析会通过命名空间,从spring.handlers配置文件中获取解析处理器进行解析处理,自定义标签的解析,在后序源码学习中会做详细解析。

2、总结

  对配置文件的解析,由命名空间是否为beans可分成2类解析,一类是默认命名空间的解析;一类是非默认命名空间的解析。
  默认命名空间的解析,对import、alias、bean、beans标签的解析:

2.1、import的解析

  通过resource属性,获取资源文件的location,判断location是绝对路径还是相对路径,若为绝对路径执行loadBeanDefinitions()方法,将导入资源文件的配置信息加载进IOC容器;若为相对路径,根据location创建Resource对象,再执行loadBeanDefinitions()流程。

2.2、alias的解析

  aslias别名、bean名称的非空校验,若别名已经被注册,则不作处理;若alias别名不允许被覆盖,抛出异常;将aslias别名与bean名称映射关系存储aliasMap中加载至IOC容器。

2.3、bean的解析

  解析bean标签,创建GenericBeanDefinition的bean定义信息,beanDefinition中包含Class对象、类名称等,创建bean定义信息的包装类对象BeanDefinitionHolder,beanDefinitionHolder包含beanDefinition信息、bean名称、bean的别名信息;将BeanDefinitionHolder注册进IOC容器 - beanDefinitionMap中。

2.4、beans的解析

  递归调用doRegisterBeanDefinitions()方法。
 
  非默认命名空间的解析,如对AOP的切点、切面等的标签的解析,根据命名空间获取spring.handlers中的解析器做解析,后续会做详细介绍。
 
 
posted @ 2022-12-21 20:53  无虑的小猪  阅读(48)  评论(0编辑  收藏  举报