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

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

服务器之家 - 编程语言 - Java教程 - springboot中@Value的工作原理说明

springboot中@Value的工作原理说明

2021-09-28 09:49spring-hz Java教程

这篇文章主要介绍了springboot中@Value的工作原理,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

我们知道springboot中的Bean组件的成员变量(属性)如果加上了@Value注解,可以从有效的配置属性资源中找到配置项进行绑定,那么这一切是怎么发生的呢?

下文将简要分析一下@Value的工作原理。

springboot版本: springboot-2.0.6.RELEASE

概述

springboot启动过程中,有两个比较重要的过程,如下:

1 扫描,解析容器中的bean注册到beanFactory上去,就像是信息登记一样。

2 实例化、初始化这些扫描到的bean。

@Value的解析就是在第二个阶段。BeanPostProcessor定义了bean初始化前后用户可以对bean进行操作的接口方法,它的一个重要实现类AutowiredAnnotationBeanPostProcessor正如javadoc所说的那样,为bean中的@Autowired和@Value注解的注入功能提供支持。

解析流程

调用链时序图

@Value解析过程中的主要调用链,我用以下时序图来表示:

springboot中@Value的工作原理说明

这里先简单介绍一下图上的几个类的作用。

AbstractAutowireCapableBeanFactory: 提供了bean创建,属性填充,自动装配,初始胡。支持自动装配构造函数,属性按名称和类型装配。实现了AutowireCapableBeanFactory接口定义的createBean方法。

AutowiredAnnotationBeanPostProcessor: 装配bean中使用注解标注的成员变量,setter方法, 任意的配置方法。比较典型的是@Autowired注解和@Value注解。

InjectionMetadata: 类的注入元数据,可能是类的方法或属性等,在AutowiredAnnotationBeanPostProcessor类中被使用。

AutowiredFieldElement: 是AutowiredAnnotationBeanPostProcessor的一个私有内部类,继承InjectionMetadata.InjectedElement,描述注解的字段。

StringValueResolver: 一个定义了处置字符串值的接口,只有一个接口方法resolveStringValue,可以用来解决占位符字符串。本文中的主要实现类在PropertySourcesPlaceholderConfigurer#processProperties方法中通过lamda表达式定义的。供ConfigurableBeanFactory类使用。

PropertySourcesPropertyResolver: 属性资源处理器,主要功能是获取PropertySources属性资源中的配置键值对。

PropertyPlaceholderHelper: 一个工具类,用来处理带有占位符的字符串。形如${name}的字符串在该工具类的帮助下,可以被用户提供的值所替代。替代途经可能通过Properties实例或者PlaceholderResolver(内部定义的接口)。

PropertyPlaceholderConfigurerResolver: 上一行所说的PlaceholderResolver接口的一个实现类,是PropertyPlaceholderConfigurer类的一个私有内部类。实现方法resolvePlaceholder中调用了外部类的resolvePlaceholder方法。

调用链说明

这里主要介绍一下调用链中的比较重要的方法。

AbstractAutowireCapableBeanFactory#populateBean方法用于填充bean属性,执行完后可获取属性装配后的bean。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {      
...
if (hasInstAwareBpps) {
    // 遍历所有InstantiationAwareBeanPostProcessor实例设置属性字段值。
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        // AutowiredAnnotationBeanPostProcessor会进入此分支
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
        //上行代码执行后,bw.getWrappedInstance()就得到了@Value注解装配属性后的bean了
            if (pvs == null) {
                return;
            }
        }
    }
}
...
}

InjectionMetadata#inject逐个装配bean的配置属性。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Collection<InjectedElement> checkedElements = this.checkedElements;
    Collection<InjectedElement> elementsToIterate =
            (checkedElements != null ? checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
        // 依次注入属性
        for (InjectedElement element : elementsToIterate) {
            if (logger.isDebugEnabled()) {
                logger.debug("Processing injected element of bean '" + beanName + "': " + element);
            }
            element.inject(target, beanName, pvs);
        }
    }
}

PropertyPlaceholderHelper#parseStringValue解析属性值

?
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
 *  一个参数示例 value = "${company.ceo}"
 *
 */
protected String parseStringValue(
        String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
    StringBuilder result = new StringBuilder(value);
    // this.placeholderPrefix = "${"
    int startIndex = value.indexOf(this.placeholderPrefix);
    while (startIndex != -1) {
        // 占位符的结束位置,以value = "${company.ceo}"为例,endIndex=13
        int endIndex = findPlaceholderEndIndex(result, startIndex);
        if (endIndex != -1) {
            // 获取{}里的真正属性名称,此例为"company.ceo"
            String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
            String originalPlaceholder = placeholder;
            if (!visitedPlaceholders.add(originalPlaceholder)) {
                throw new IllegalArgumentException(
                        "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
            }
            // Recursive invocation, parsing placeholders contained in the placeholder key.
            // 递归调用本方法,因为属性键中可能仍然有占位符
            placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
            // Now obtain the value for the fully resolved key...
            // 获取属性键placeholder对应的属性值
            String propVal = placeholderResolver.resolvePlaceholder(placeholder);
            // 此处逻辑是当company.ceo=${bi:li}时,company.ceo最终被li所替代的原因
            // 所以配置文件中,最好不要出现类似${}的东西,因为它本身就会被spring框架所解析
            if (propVal == null && this.valueSeparator != null) {
                int separatorIndex = placeholder.indexOf(this.valueSeparator);
                if (separatorIndex != -1) {
                    String actualPlaceholder = placeholder.substring(0, separatorIndex);
                    String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                    propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                    if (propVal == null) {
                        propVal = defaultValue;
                    }
                }
            }
            if (propVal != null) {
                // Recursive invocation, parsing placeholders contained in the
                // previously resolved placeholder value.
                propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                // 将${company.ceo}替换为li
                result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                if (logger.isTraceEnabled()) {
                    logger.trace("Resolved placeholder '" + placeholder + "'");
                }
                startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
            }
            else if (this.ignoreUnresolvablePlaceholders) {
                // Proceed with unprocessed value.
                startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
            }
            else {
                throw new IllegalArgumentException("Could not resolve placeholder '" +
                        placeholder + "'" + " in value \"" + value + "\"");
            }
            visitedPlaceholders.remove(originalPlaceholder);
        }
        else {
            startIndex = -1;
        }
    }
    return result.toString();
}

总结

@Value注解标注的bean属性装配是依靠AutowiredAnnotationBeanPostProcessor在bean的实例化、初始化阶段完成的。以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/gs_albb/article/details/85401720

延伸 · 阅读

精彩推荐
  • Java教程浅谈java 中equals和==的区别

    浅谈java 中equals和==的区别

    这篇文章主要介绍了java 中equals和==的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小...

    独特润许多人5982021-07-21
  • Java教程mybatis批量新增、删除、查询和修改方式

    mybatis批量新增、删除、查询和修改方式

    这篇文章主要介绍了mybatis批量新增、删除、查询和修改方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    xuforeverlove7492022-01-24
  • Java教程Java开发常见异常及解决办法详解

    Java开发常见异常及解决办法详解

    这篇文章主要介绍了java程序常见异常及处理汇总,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考...

    cutercorley12252021-12-18
  • Java教程Spring Cloud Gateway 如何修改HTTP响应信息

    Spring Cloud Gateway 如何修改HTTP响应信息

    这篇文章主要介绍了Spring Cloud Gateway 修改HTTP响应信息的方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    帷幄庸者13712021-10-13
  • Java教程Spring 6.0 将停止支持 Freemarker 和 JSP

    Spring 6.0 将停止支持 Freemarker 和 JSP

    Spring Framework 6.0 第一个里程碑版本已经发布,目前已经可以从Spring Repo获取。这里有一些新变更我们可以提前了解一下。...

    码农小胖哥12642021-12-31
  • Java教程浅谈sql_@SelectProvider及使用注意说明

    浅谈sql_@SelectProvider及使用注意说明

    这篇文章主要介绍了sql_@SelectProvider及使用注意说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    icecoola_6892021-11-04
  • Java教程mybatis调用存储过程的实例代码

    mybatis调用存储过程的实例代码

    这篇文章主要介绍了mybatis调用存储过程的实例,非常不错,具有参考借鉴价值,需要的朋友可以参考下...

    动力节点11732021-01-25
  • Java教程二进制中1的个数

    二进制中1的个数

    这篇文章介绍了二进制中1的个数,有需要的朋友可以参考一下 ...

    java之家2662019-10-15