@Value 源码浅析

缘起

@Value 只要是学习过javaconfig的童鞋一定不陌生. 尤其是学过springboot的同学. 那么@Value 注入是怎么实现的呢? 于是我保证兴趣一探究竟.

分析

首先,选用的版本是

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>

其次我们分析一下@Value注入的流程

因为下面的代码

1
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);

相当于是启动了ioc容器, 所以进入了下面的构造器源码

1
2
3
4
5
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}

​ 源码1

从refresh方法进入, 直至如下org.springframework.context.support.AbstractApplicationContext.refresh()的如下源码

1
2
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

​ 源码2

进入上述finishBeanFactoryInitialization方法, 进入到如下源码

1
2
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();

​ 源码3

上面继续进入org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons()源码

1
getBean(beanName);

其中beanName是bean在ioc容器中的id.

然后继续跟到了org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(String, RootBeanDefinition, Object[]) 的源码

1
2
3
4
5
6
7
8
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}

​ 源码4

这是我们熟悉的源码(参见【1】). 进入到第四行的populateBean方法中去. 我们来到了

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(PropertyValues, PropertyDescriptor[], Object, String) 的源码

1
metadata.inject(bean, beanName, pvs);

​ 源码5

其中 metadata 是通过org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(PropertyValues, PropertyDescriptor[], Object, String) 的第一行代码

1
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);

得到的. 其中虽然没有仔细跟findAutowiringMetadata的源码,但是顾名思义, 就是找到所有需要注入的属性. 这里debug(demo参见【2】)得到的metadata是

1
[AutowiredFieldElement for private java.lang.String com.yfs.bean.Person.name, AutowiredFieldElement for private java.lang.Integer com.yfs.bean.Person.age, AutowiredFieldElement for private java.lang.String com.yfs.bean.Person.nickname]

表明我们要注入的是三个属性. 然后继续跟进去就是

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement.inject(Object, String, PropertyValues) 的如下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
...
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
...
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
}

​ 源码6

即获取要注入值的field(第三行), 以及值本身value(第七行),最后通过反射注入值(第12行).

而获取value的代码是源码6的第七行(解析依赖). 跟进去就会找到我们感兴趣的如下源码

org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(String)

这个方法就是通过@Value的属性值从Environment中获取值的方法. 其源码很简单.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public String resolveEmbeddedValue(String value) {
if (value == null) {
return null;
}
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
if (result == null) {
return null;
}
}
return result;
}

​ 源码7

对于【2】中的demo, 三次进入此源码的value是 “影法师”, “#{20-2}”(这是一个SpEL表达式), “${person.nickname}”,因为我们的bean——Person如下

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person {

// 常量
@Value("影法师")
private String name;
// SpEL
@Value("#{20-2}")
private Integer age;

// 配置文件
@Value("${person.nickname}")
private String nickname;
...

回到源码7, 它其实是使用内嵌的 embeddedValueResolvers(内嵌值解析器,真正类型是个 AbstractApplicationContext\$1, 即AbstractApplicationContext的一个内部类——StringValueResolver接口类型. 这是内嵌值解析器的默认类型, 添加源码在org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(ConfigurableListableBeanFactory)的源码的第11行)解析”影法师”、”#{20-2}”、”${person.nickname}” 这三个表达式. 而真正解析的时候用的StringValueResolver. 而 StringValueResolver解析的逻辑是

1
2
3
public String resolveStringValue(String strVal) {
return getEnvironment().resolvePlaceholders(strVal); // debug发现其实environment就是一个AbstractEnvironment 类型
}

​ 源码8

即直接从Environment中解析占位符strVal.

  1. 对于”影法师”, environment直接使用 PropertySourcesPropertyResolver解析之.而PropertySourcesPropertyResolver直接使用PropertyPlaceholderHelper进行解析(注意,这个PropertyPlaceholderHelper也是懒加载的, 即第一次来的是null, 只有第二次来才不为null). 最后来到org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(String, BeanExpressionContext) 的如下源码(真正开始求@Value中表达式的值了)

    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
    @Override
    public Object evaluate(String value, BeanExpressionContext evalContext) throws BeansException {
    if (!StringUtils.hasLength(value)) {
    return value;
    }
    try {
    // 从缓存中获取表达式
    Expression expr = this.expressionCache.get(value);
    if (expr == null) { // 第一次来需要缓存表达式
    expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
    this.expressionCache.put(value, expr);
    }
    // 求值上下文
    StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
    if (sec == null) { // 第一次来,上下文是空的.需要做一系列的初始化
    sec = new StandardEvaluationContext();
    sec.setRootObject(evalContext);
    sec.addPropertyAccessor(new BeanExpressionContextAccessor());
    sec.addPropertyAccessor(new BeanFactoryAccessor());
    sec.addPropertyAccessor(new MapAccessor());
    sec.addPropertyAccessor(new EnvironmentAccessor());
    sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
    sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
    ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
    if (conversionService != null) {
    sec.setTypeConverter(new StandardTypeConverter(conversionService));
    }
    customizeEvaluationContext(sec);
    this.evaluationCache.put(evalContext, sec);
    }
    // 这里返回求出的表达式的真正的值
    return expr.getValue(sec);
    }
    catch (Throwable ex) {
    throw new BeanExpressionException("Expression parsing failed", ex);
    }
    }

    ​ 源码9

    求完值之后,就回到源码6进行注入了.

  2. 对于SpEL #{20-2}而言, 和1是一模一样的代码路径也是来到了源码9,传入的参数是 #{20-2}, 因为是第二次来到了源码9, 所以sec求值上下文不再是空,但是expr因为是新的(#{20-2}之前从未求过), 所以依旧求出之后缓存一份表达式. 源码9返回的就是18, 然后就再次返回源码6进行注入了.

  3. 对于 文件占位符 ${person.nickname} , 注意,对于3,和1和2是不一样的. 因为3的字符串是以 \${ 开头的. 所以会走的代码路径和1 和 2略有不同. 源码见下(是org.springframework.util.PropertyPlaceholderHelper.parseStringValue(String, PlaceholderResolver, Set\)的源码, 因为上面说到了,1和2本质都是使用PropertyPlaceholderHelper作为工具类进行值解析的)

    1
    2
    3
    4
    5
    6
    7
    8
    protected String parseStringValue(
    String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

    StringBuilder result = new StringBuilder(value);

    int startIndex = value.indexOf(this.placeholderPrefix);
    while (startIndex != -1) {
    ...

    其中对于1和2,startIndex都是-1,因为placeholderPrefix是 ${.(该常量在AbstractPropertyResolver中规定, 并且在第一次初始化PropertyPlaceholderHelper的时候(回忆我们说过PropertyPlaceholderHelper是懒加载的)使用的). 所以对于1和2都不会走while里面的逻辑. 而是直接返回了. 对于3而言, 就会走while里面的逻辑,while里面的逻辑就是先扒掉 ${}(变成 person.nickname), 然后使用PropertyPlaceholderHelper.PlaceholderResolver里面的接口方法resolvePlaceholder解析person.nickname, 源码见下

    1
    2
    3
    public String resolvePlaceholder(String placeholderName) {
    return getPropertyAsRawString(placeholderName);
    }

    跟进去getPropertyAsRawString方法, 我们会来到org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(String, Class\, boolean)方法, 该方法使用 propertySources(一个MutablePropertySources,它是容器初始化的时候加载完毕的),来求person.nickname的值的. 得到了值, 最后返回源码6进行注入. getProperty的源码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) { // propertySources 是容器加载的时候加载的
    // 遍历所有属性资源寻找我们要找的 person.nickname
    for (PropertySource<?> propertySource : this.propertySources) {
    ...
    Object value = propertySource.getProperty(key); // 核心代码——求值
    if (value != null) {
    ...
    // 做必要的类型转换
    return convertValueIfNecessary(value, targetValueType);
    }
    }
    }
    ...
    return null;
    }

    ​ 源码12

知道了@Value 的注入过程, 我们自然想知道这些配置文件的值是如何注入environment的.

这就涉及ioc容器的启动了. 首先,运行如下代码,就是在初始化ioc容器

1
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);

它会调用AnnotationConfigApplicationContext的构造器

1
2
3
4
5
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}

​ 源码10

上面源码中的refresh方法就会进行之前所说的@Value注入等一系列过程. 但是初始化environment是在 this() 中进行的.

1
2
3
4
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}

第一行我猜测是扫描注解注入的bean,第二行我猜测是扫描xml文件注入的bean. 最后都会调用到org.springframework.context.support.AbstractApplicationContext.getEnvironment()

1
2
3
4
5
6
7
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}

其中createEnvironment方法仅仅是简单返回一个StandardEnvironment而已.

然后进入到源码10中的refresh方法, 并且一路来到了

org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClass, SourceClass)

其中有一行源码

1
processPropertySource(propertySource);

​ 源码11

里面的propertySource在debug下为

1
{name=, value=[classpath:person.properties], factory=interface org.springframework.core.io.support.PropertySourceFactory, encoding=, ignoreResourceNotFound=false}

表明是加入的资源. 里面有 person.properties,它是classpath里面的东西(是我们的自定义的配置文件). 内容为

1
person.nickname=草

所以我们就不难知道上面的源码11的作用是将这些配置文件的资源加入到environment中去. 在源码11中的processPropertySource方法中,核心代码是

1
2
3
4
5
6
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}

第四行得到resource,代表的就是资源. 然后通过addPropertySource方法将resource加入到environment中去,于是我们继续跟第五行的addPropertySource方法. 该方法的核心源码是

1
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();

即得到的propertySources是从environment中获取的,然后整个方法就是对这个MutablePropertySources进行add操作(注意哦,本博文之前出现过MutablePropertySources哦,见源码12~). 即addPropertySource方法中的如下源码

1
propertySources.addLast(propertySource);

所以我们就知道了properties文件是如何加载进入environment的. 并且后面在源码12中解析出占位符的值再通过源码6进行注入.

参考

【1】https://yfsyfs.github.io/2019/06/10/BeanPostProcessor-%E5%8E%9F%E7%90%86%E6%B5%85%E6%9E%90/

【2】https://github.com/yfsyfs/backend/tree/master/spring-annotation-value