Spring|Spring Bean生命周期之Bean元信息的配置与解析阶段详解

目录

  • BeanDefinitionReader体系
    • BeanDefinitionReader接口定义
  • 元信息配置与解析方式
    • XmlBeanDefinitionReader元信息解析源码分析
      • AnnotatedBeanDefinitionReader元信息解析源码分析
        • 总结
          写在前面
          注:本文章使用的 SpringBoot 版本为 2.2.4.RELEASE,其 Spring 版本为 5.2.3.RELEASE
          虽然Bean的创建可以采用BeanDefinition API 也可以直接采用注解方式,但从学习角度出发这里主要以API形式创建Bean。下面以一段Bean创建的示例来引出讨论的议题。
          public class XmlBeanMetaDataConfigDemo {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); int beanDefinitions = beanDefinitionReader.loadBeanDefinitions("META-INF/spring.xml"); System.out.println("加载的BeanDefinition个数为:" + beanDefinitions); //User就是普通的POJO类 这里不再给出User定义User user = beanFactory.getBean("user", User.class); System.out.println(user); }}


          上面代码 是使用BeanDefinitionReader去加载XML文件中的Bean定义

          BeanDefinitionReader体系 先来看一下BeanDefinitionReader的接口定义以及继承关系
          Spring|Spring Bean生命周期之Bean元信息的配置与解析阶段详解
          文章图片

          通过上面的类图我们发现BeanDefinitionReader有三个实现类,可以看出针对BeanDefiniiton的不同载体 均提供了解析手段,有XML形式的、有Properties形式的等等。

          BeanDefinitionReader接口定义
          public interface BeanDefinitionReader {//返回注册了当前BeanDefinition的 BeanFactory BeanDefinitionRegistry getRegistry(); @Nullable ResourceLoader getResourceLoader(); @Nullable ClassLoader getBeanClassLoader(); //BeanName 生成器,默认是DefaultBeanNameGenerator BeanNameGenerator getBeanNameGenerator(); //从指定资源中加载BeanDefinition,并返回加载到的BeanDefinition的个数 int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; //从指定资源路径中中加载BeanDefinition,并返回加载到的BeanDefinition的个数 int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }

          由于Bean元信息的配置与解析是息息相关的,下面的一些例子也是将它们揉在一起讨论的。

          元信息配置与解析方式 1、API方式
          这种方式直接采用BeanDefinitionAPI 来构成Bean元信息,并将其注入到IoC容器中,这里主要使用到BeanDefinitionBuilderGenericBeanDefinition两种方式来创建Bean元信息
          关于BeanDefinition这个议题的讨论会放在其他篇章中,这里不再赘述了。
          public class ApiBeanMetaDataConfigDemo {public static void main(String[] args) {//创建注解相关的上下文AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(); //创建GenericBeanDefinitionGenericBeanDefinition beanDefinition=new GenericBeanDefinition(); //设置BeanClassbeanDefinition.setBeanClass(User.class); //设置属性MutablePropertyValues propertyValues=new MutablePropertyValues(); propertyValues.addPropertyValue("name","我就是我"); propertyValues.addPropertyValue("age",18); beanDefinition.setPropertyValues(propertyValues); //注册BeanDefinitioncontext.registerBeanDefinition("user",beanDefinition); //刷新IoC容器context.refresh(); //获取BeanUser user = context.getBean("user", User.class); System.out.println(user); //关闭上下文context.close(); }}

          2、面向XML配置
          从XML配置资源处 加载BeanDefinition

          public class XmlBeanMetaDataConfigDemo {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //加载指定文件的BeanDefinition,并获取加载地BeanDefinition个数int beanDefinitions = beanDefinitionReader.loadBeanDefinitions("META-INF/spring.xml"); System.out.println("加载的BeanDefinition个数为:" + beanDefinitions); User user = beanFactory.getBean("user", User.class); System.out.println(user); }}

          3、面向Properties配置
          这种配置方式不太常用,配置资源时需要遵守规则,配置规则可参考PropertiesBeanDefinitionReader注释文档,其规则如下
          Properties 属性名 使用场景
          (class) Bean 类全称限定名
          (abstract) 是否为抽象的 BeanDefinition
          (parent) 指定 parent BeanDefinition 名称
          (lazy-init) 是否为延迟初始化
          (ref) 引用其他 Bean 的名称
          (scope) 设置 Bean 的 scope 属性
          ${n} n 表示第 n+1 个构造器参数
          ## 指定BeanClassuser.(class)=com.wojiushiwo.dto.User## 属性user.name=我就是我user.age=19

          public class PropertiesBeanMetaDataConfigDemo {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); PropertiesBeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(beanFactory); ClassPathResource classPathResource=new ClassPathResource("META-INF/user.properties"); EncodedResource resource=new EncodedResource(classPathResource,"UTF-8"); int beanDefinitions = beanDefinitionReader.loadBeanDefinitions(resource); System.out.println("加载的BeanDefinition个数为:" + beanDefinitions); User user = beanFactory.getBean("user", User.class); System.out.println(user); }}

          上面 Properties文件默认读写编码为ISO-8859-1 因此这种直接加载方式会出现中文乱码,可通过加载在加载资源时指定编码方式来解决
          4、面向注解
          比如@Autowired@Bean@Component@Configuration等,这些在当下都比较常用不再赘述

          XmlBeanDefinitionReader元信息解析 源码分析 Spring|Spring Bean生命周期之Bean元信息的配置与解析阶段详解
          文章图片

          下面就XmlBeanDefinitionReader调用链中比较重要的地方进行分析
          protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {//解析Xml文件生成Document,这里不再展开Document doc = doLoadDocument(inputSource, resource); // 解析Document 注册BeanDefinitionint count = registerBeanDefinitions(doc, resource); //省略日志打印return count; }catch (BeanDefinitionStoreException ex) {//... 异常及日志} }

          public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {//获取DefaultBeanDefinitionReaderBeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //获取IoC容器中 已经存在的BeanDefinition的个数int countBefore = getRegistry().getBeanDefinitionCount(); //这里实际上执行解析Document文档树 注册BeanDefinitiondocumentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //返回此次加载的BeanDefinition个数return getRegistry().getBeanDefinitionCount() - countBefore; }

          protected void doRegisterBeanDefinitions(Element root) {BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); // namespace=http://www.springframework.org/schema/beans 表示为默认命名空间 则使用默认的解析方式去解析元素,否则将采用NamespaceHandler去解析if (this.delegate.isDefaultNamespace(root)) {//获取profile属性,profile与Spring配置环境有关系String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); //如果配置了profileif (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); //如果当前Environment环境与profile不匹配 则流程结束if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {//省略日志return; }}}//解析xml前置操作preProcessXml(root); //解析xmlparseBeanDefinitions(root, this.delegate); //解析xml后置操作postProcessXml(root); this.delegate = parent; }

          关于上面源码分析中profile以及NameSpace的理解 请看这里的XML

          可以看出 xmlns所指代的就是namespace,而profile也可以配置在beans标签上,其中
          http://www.springframework.org/schema/beans

          表示默认命名空间。因为Spring允许自定义标签,所以通过是否为默认命名空间作为判断依据来选择使用不同的解析方式去解析标签
          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 {// 使用NamespaceHandler去解析标签,比如ContextNamespaceHandler去解析的标签等delegate.parseCustomElement(ele); }}}}else {// 使用NamespaceHandler去解析标签,比如ContextNamespaceHandler去解析的标签等delegate.parseCustomElement(root); } }

          上面的代码逻辑,根据是否为默认命名空间从而选择不同的解析方式,自定义标签或非默认命名空间指令 需要继承NamespaceHandler去实现自己的标签解析方式
          非默认命名空间指令举例

          这里直接看针对默认命名空间的解析代码
          private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {//如果是import指令if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {importBeanDefinitionResource(ele); }//如果是alias指令else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {processAliasRegistration(ele); }//如果是bean指令else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {processBeanDefinition(ele, delegate); }else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// recursedoRegisterBeanDefinitions(ele); } }

          针对bean指令
          protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) {bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try {//使用BeanRegistry对BeanDefinition进行注册BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); }catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex); }// Send registration event.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }

          我们再来梳理下主要流程:
          1、解析Xml文件 生成Document
          2、针对Document命名空间进行标签解析
          • 默认命名空间 标签解析
          • 非默认命名空间或自定义标签 自行实现NamespaceHandler去解析(Spring 内置了一些NamespaceHandler解析自定义标签)
          3、使用BeanRegistry对BeanDefinition进行注册

          AnnotatedBeanDefinitionReader元信息解析 源码分析 AnnotatedBeanDefinitionReader从名称上看它似乎与BeanDefinitionReader有千丝万缕的关系,实质上二者没有关系。
          AnnotatedBeanDefinitionReader主要是 对注解Bean进行解析的。
          先举例说明下
          @Configurationpublic class BeanInitializationDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); // 注册 Configuration Class(配置类)applicationContext.register(BeanInitializationDemo.class); // 启动 Spring 应用上下文applicationContext.refresh(); applicationContext.close(); }}

          借助上面的例子我们看一下其调用流程
          Spring|Spring Bean生命周期之Bean元信息的配置与解析阶段详解
          文章图片

          以上面的例子来看,AnnotationBeanDefinitionReader的创建是在AnnotationConfigApplicationContext构造函数中进行的。
          public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner; public AnnotationConfigApplicationContext() {//创建AnnotatedBeanDefinitionReaderthis.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); }}

          public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// 获取BeanNameString beanName = definitionHolder.getBeanName(); //注册bd到IoC容器registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 如果bean存在别名,则将beanName与alias的关系也存起来String[] aliases = definitionHolder.getAliases(); if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias); }} }

          透过上面XmlBeanDefinitionReaderAnnotationBeanDefinitionReader对BeanDefinition的解析来看,最终BeanDefinition的注册都指向了BeanDefinitionReaderUtils.registerBeanDefinition。我们先来大概看一下代码实现
          public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// 获取BeanNameString beanName = definitionHolder.getBeanName(); //注册bd到IoC容器registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 如果bean存在别名,则将beanName与alias的关系也存起来String[] aliases = definitionHolder.getAliases(); if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias); }} }


          总结 【Spring|Spring Bean生命周期之Bean元信息的配置与解析阶段详解】本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

            推荐阅读