服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Spring Bean生命周期之Bean元信息的配置与解析阶段详解

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

2022-08-24 11:44码农的进阶之路 Java教程

这篇文章主要为大家详细介绍了Spring Bean生命周期之Bean元信息的配置与解析阶段,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

写在前面

注:本文章使用的 SpringBoot 版本为 2.2.4.RELEASE,其 Spring 版本为 5.2.3.RELEASE

虽然Bean的创建可以采用BeanDefinition API 也可以直接采用注解方式,但从学习角度出发这里主要以API形式创建Bean。下面以一段Bean创建的示例来引出讨论的议题。

?
1
2
3
4
5
6
7
8
9
10
11
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);
    }
}
?
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.wojiushiwo.dto.User">
        <property name="name" value="wojiushiwo"/>
        <property name="age" value="18"/>
    </bean>
</beans>

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

BeanDefinitionReader体系

先来看一下BeanDefinitionReader的接口定义以及继承关系

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

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

BeanDefinitionReader接口定义

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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这个议题的讨论会放在其他篇章中,这里不再赘述了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ApiBeanMetaDataConfigDemo {
    public static void main(String[] args) {
        //创建注解相关的上下文
        AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();
        //创建GenericBeanDefinition          
        GenericBeanDefinition beanDefinition=new GenericBeanDefinition();
        //设置BeanClass
        beanDefinition.setBeanClass(User.class);
        //设置属性
        MutablePropertyValues propertyValues=new MutablePropertyValues();
        propertyValues.addPropertyValue("name","我就是我");
        propertyValues.addPropertyValue("age",18);
        beanDefinition.setPropertyValues(propertyValues);
        //注册BeanDefinition
        context.registerBeanDefinition("user",beanDefinition);
        //刷新IoC容器
        context.refresh();
        //获取Bean
        User user = context.getBean("user", User.class);
        System.out.println(user);
        //关闭上下文
        context.close();
    }
}

2、面向XML配置

从XML配置资源处 加载BeanDefinition

?
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <bean id="user" class="com.wojiushiwo.dto.User">
        <property name="name" value="wojiushiwo"/>
        <property name="age" value="18"/>
    </bean>
</beans>
?
1
2
3
4
5
6
7
8
9
10
11
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 个构造器参数
?
1
2
3
4
5
## 指定BeanClass
user.(class)=com.wojiushiwo.dto.User
## 属性
user.name=我就是我
user.age=19
?
1
2
3
4
5
6
7
8
9
10
11
12
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 Bean生命周期之Bean元信息的配置与解析阶段详解

下面就XmlBeanDefinitionReader调用链中比较重要的地方进行分析

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            //解析Xml文件生成Document,这里不再展开
            Document doc = doLoadDocument(inputSource, resource);
            // 解析Document 注册BeanDefinition
            int count = registerBeanDefinitions(doc, resource);
            //省略日志打印
            return count;
        }
        catch (BeanDefinitionStoreException ex) {
            //... 异常及日志
            }
    }
?
1
2
3
4
5
6
7
8
9
10
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //获取DefaultBeanDefinitionReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //获取IoC容器中 已经存在的BeanDefinition的个数
        int countBefore = getRegistry().getBeanDefinitionCount();
        //这里实际上执行解析Document文档树 注册BeanDefinition
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        //返回此次加载的BeanDefinition个数
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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);
            //如果配置了profile
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      //如果当前Environment环境与profile不匹配 则流程结束
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    //省略日志
                    return;
                }
            }
        }
        //解析xml前置操作    
        preProcessXml(root);
        //解析xml
        parseBeanDefinitions(root, this.delegate);
        //解析xml后置操作
        postProcessXml(root);
        this.delegate = parent;
    }

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

?
1
2
3
4
5
6
7
8
9
10
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd" profile="dev">
        <!-- 一些配置-->
</beans>

可以看出 xmlns所指代的就是namespace,而profile也可以配置在beans标签上,其中

?
1
http://www.springframework.org/schema/beans

表示默认命名空间。因为Spring允许自定义标签,所以通过是否为默认命名空间作为判断依据来选择使用不同的解析方式去解析标签

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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去解析<context:xx>的标签等
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
          // 使用NamespaceHandler去解析标签,比如ContextNamespaceHandler去解析<context:xx>的标签等
            delegate.parseCustomElement(root);
        }
    }

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

非默认命名空间指令举例

?
1
2
<context:component-scan base-package="com.wojiushiwo"/><aop:xx></aop><context:component-scan base-package="com.wojiushiwo"/>
<aop:xx></aop>

这里直接看针对默认命名空间的解析代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

针对bean指令

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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进行解析的。

先举例说明下

?
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class BeanInitializationDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class(配置类)
      applicationContext.register(BeanInitializationDemo.class);
        // 启动 Spring 应用上下文
        applicationContext.refresh();
        applicationContext.close();
    }
}

借助上面的例子我们看一下其调用流程

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

以上面的例子来看,AnnotationBeanDefinitionReader的创建是在AnnotationConfigApplicationContext构造函数中进行的。

?
1
2
3
4
5
6
7
8
9
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    private final AnnotatedBeanDefinitionReader reader;
    private final ClassPathBeanDefinitionScanner scanner;
    public AnnotationConfigApplicationContext() {
      //创建AnnotatedBeanDefinitionReader
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }
}  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {
        // 获取BeanName
        String 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。我们先来大概看一下代码实现

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {
        // 获取BeanName
        String 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);
            }
        }
    }

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!  

原文链接:https://blog.csdn.net/zyxwvuuvwxyz/article/details/123204640

延伸 · 阅读

精彩推荐
  • Java教程SpringBoot JPA实现查询多值

    SpringBoot JPA实现查询多值

    这篇文章主要为大家详细介绍了SpringBoot JPA实现查询多值,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    零晨三点半4982021-05-28
  • Java教程idea 实现搜索jdk中的类和包操作

    idea 实现搜索jdk中的类和包操作

    这篇文章主要介绍了idea 实现搜索jdk中的类和包操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    输微12122021-08-06
  • Java教程Java回调方法详解

    Java回调方法详解

    本篇文章主要介绍了Java回调方法的步骤、回调的例子、异步回调与同步回调、回调方法在通信中的应用等。具有一定的参考价值,下面跟着小编一起来看下...

    byhieg3232020-07-22
  • Java教程Java高并发测试框架JCStress详解

    Java高并发测试框架JCStress详解

    这篇文章主要介绍了Java高并发测试框架JCStress,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...

    陈皮的JavaLib6832021-09-02
  • Java教程关于Java Spring三级缓存和循环依赖的深入理解

    关于Java Spring三级缓存和循环依赖的深入理解

    对于循环依赖,我相信读者无论只是听过也好,还是有过了解也好,至少都有所接触。但是我发现目前许多博客对于循环依赖的讲解并不清楚,都提到了...

    小明の学习心得11992022-01-10
  • Java教程浅谈选择结构if语句和switch语句的区别

    浅谈选择结构if语句和switch语句的区别

    下面小编就为大家带来一篇浅谈选择结构if语句和switch语句的区别。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    java教程网4552020-05-23
  • Java教程对比Java设计模式编程中的状态模式和策略模式

    对比Java设计模式编程中的状态模式和策略模式

    这篇文章主要介绍了Java设计模式编程中的状态模式和策略模式对比,文中列举了两种模式的相似点和不同点,并都举了代码的实例作为参照,需要的朋友可以参...

    花名有孚5152020-04-20
  • Java教程Java源码解析之TypeVariable详解

    Java源码解析之TypeVariable详解

    这篇文章主要介绍了Java源码解析之TypeVariable详解,具有一定参考价值,需要的朋友可以了解下。...

    青楼有罪8792021-01-25