[Spring] 深入理解: Spring @Value 解析、注入时机及原理

内容摘要:
@Value的使用及它是什么时候解析的并且解析后是如何注入值的?

1 @Value的使用

简述

  • @Value 注解可用来将外部的值动态注入到 Bean 中,在 @Value 注解中,可以使 ${}#{} ,它们的区别如下:

(1)@Value("${}"):可以获取对应属性文件中定义的属性值。
(2)@Value("#{}"):表示 SpEl 表达式通常用来获取 bean 的属性,或者调用 bean 的某个方法。

  • 根据注入的内容来源,@Value属性注入功能可以分为两种:
  • 通过【配置文件】进行属性注入
  • 通过【非配置文件】进行属性注入

基于配置文件的注入

单个注入

# application.properties
user.nick = kuku
@RestController
public class DemoController {

    @Value("${user.nick:如果需要默认值格式(:默认值)}")
    private String name;
}

注意点:当文件类型是 xx.properties 是如果存在中文的话,比如:

spring.profiles.active=haha
user.nick=大米

就会出现乱码,这是因为在 SpringBoot 的 CharacterReader 类中,默认的编码格式是 ISO-8859-1 ,该类负责 .properties 文件中系统属性的读取。
如果系统属性包含中文字符,就会出现乱码。

  • 解决方法:

方法1: 改为yml、yaml格式的 (可参考)
方法2: 配置文件里中文转义掉

在线工具 : http://www.jsons.cn/unicode/
方法3: 自己使用的时候手动转换
这里说下yml为什么可以,因为.yml或.yaml格式的配置文件,最终会使用 UnicodeReader 类进行解析
它的init方法中,首先读取 BOM 文件头信息,如果头信息中有UTF8UTF16BEUTF16LE,就采用对应的编码,如果没有,则采用默认UTF8编码。

静态变量注入

  • 默认被static修饰的变量通过@Value会注入失败。

  • 解决方法: 将@Value注解写到方法上

private static String name;

@Value("${user.nick:如果需要默认值格式(:默认值)}")
public void setName(String name) {
    this.name = name;
}

基于非配置文件的注入

基本类型

  • @Value注解对这8种基本类型和相应的包装类,有非常良好的支持,例如:
@Value("${user.test:3000}")
private Integer num;

@Value("${user.test:3000}")
private int num;

数组

但只用上面的基本类型是不够的,特别是很多需要批量处理数据的场景中。
这时候可以使用数组,它在日常开发中使用的频率很高。我们在定义数组时可以这样写:

@Value("${kuku.test:1,2,3,4,5}")
private int[] array;
  • spring默认使用英文逗号分隔参数值。

如果用空格分隔,例如:@Value("${kuku.test:1 2 3 4 5}") spring会自动去掉空格,导致数据中只有一个值:12345

  • 如果我们把数组定义成:short、int、long、char、string类型,spring是可以正常注入属性值的。

  • 但如果把数组定义成:float、double 类型,启动项目时就会直接报错

  • 如果使用int的包装类 Integer[],比如:

@Value("${user.test:1,2,3,4,5}")
private Integer[] array;

启动项目时,同样会报异常。

  • 此外,定义数组时一定要注意属性值的类型,必须数组内各元素值的类型完全一致才可以。

如果出现下面这种情况:

@Value("${user.test:1.0,abc,3,4,5}")
private int[] array;

属性值中包含了1.0和abc,显然都无法将该字符串转换成int。

集合类

  • 我们看看List是如何注入属性值的:
user.test = 10,11,12,13

@Value("${user.test}")
private List<Integer> test;

基于 Spring EL 表达式

基于EL表达式,注入 Bean 对象 ≈ @Autowired / @Resource

  • 注入bean,一般都是用的@Autowired或者@Resource注解,@Value注解也可以注入bean,它是这么做的:
@Value("#{roleService}")
private RoleService roleService;
  • 通过 EL 表达式,@Value注解已经可以注入bean了。

既然能够拿到bean实例,接下来,可以再进一步,获取成员变量、常量、方法、静态方法:

@Value("#{roleService.DEFAULT_AGE}")
private int myAge;

基于EL表达式,引用静态类、静态属性、静态方法

  • 前面的内容都是基于bean的,但有时我们需要调用静态类

比如:Math、xxxUtil等静态工具类的方法,可以这么写:

// 注入系统的路径分隔符到path中
@Value("#{T(java.io.File).separator}")
private String path;

// 注入一个随机数到randomValue中
@Value("#{T(java.lang.Math).random()}")
private double randomValue;

基于EL表达式,逻辑运算

还可以进行逻辑运算:

// 拼接
@Value("#{roleService.roleName + '' + roleService.DEFAULT_AGE}")
private String value;

// 逻辑判断
@Value("#{roleService.DEFAULT_AGE > 16 and roleService.roleName.equals('苏三')}")
private String operation;

小结:${}配置属性 与 #{} EL表达式

上面这么多@Value的用法,归根揭底就是${}和#{}的用法,我们来看看两者的区别:

  • ${}

主要用于获取配置文件中的属性值,可以设置默认值。如果在配置文件中找不到属性的配置,则注入时用默认值,如果都没有会直接报错

  • #{}

主要用于通过spring的EL表达式,获取bean的属性,或者调用bean的某个方法,还有调用类的静态常量和静态方法,如果是调用类的静态方法,则需要加T(包名 + 方法名称)

2 AutowiredAnnotationBeanPostProcessor 类介绍

AutowiredAnnotationBeanPostProcessor 简介

package org.springframework.beans.factory.annotation;

public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
	...
}
  • 首先,解析的都是我们的Spring管理的Bean,我们的Bean又有配置型 Configuration、服务型 Controller、Service 等的,但他们都是基于 @Component 的。

那解析@Value的时候是什么时候呢?
其实就是创建 Bean的时候,也就是实例化的时候,而实例化又分懒加载的、和随着SpringBoot启动就会创建的,在刷新方法里的 finishBeanFactoryInitialization 会对不是懒加载的Bean进行实例化。
这就涉及到Bean的生命周期了,其实解析属性注入都是通过后置处理器进行的。

  • 解析:doCreateBean 方法里的 applyMergedBeanDefinitionPostProcessors 执行后置处理器进行收集,实际收集的处理器是:AutowiredAnnotationBeanPostProcessor
  • 注入:populateBean 方法里的 postProcessProperties 执行后置处理器进行注入,实际注入的处理器还是:AutowiredAnnotationBeanPostProcessor

我们先看下 AutowiredAnnotationBeanPostProcessor 类图:

  • 那你们知道这个后置处理是什么时候加载进来的么?我们来看下:

@Value 的初始化与解析

AutowiredAnnotationBeanPostProcessor 构造器 : @Value 的初始化与解析

  • 我们先看下 AutowiredAnnotationBeanPostProcessor 构造方法:
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);


public AutowiredAnnotationBeanPostProcessor() {
    this.autowiredAnnotationTypes.add(Autowired.class);
    this.autowiredAnnotationTypes.add(Value.class);
    try {
        this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
                ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
        logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
    }
    catch (ClassNotFoundException ex) {
        // JSR-330 API not available - simply skip.
    }
}

可以看到实例化的时候,已经把 @Autowired@Value 初始化到 autowiredAnnotationTypes 集合中了。

AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition : 解析、注入、缓存

我们先看下解析的方法:

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 解析注解信息并缓存,注入的时候直接拿
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 将已经处理过的注入点缓存到 bd.externallyManagedConfigMembers 中,下次再处理时不会处理已经缓存的注入点
    metadata.checkConfigMembers(beanDefinition);
}

AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata

主要方法就是 findAutowiringMetadata ,我们进去看一下:

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // Fall back to class name as cache key, for backwards compatibility with custom callers.
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // 先从缓存中拿 双重检查 Quick check on the concurrent map first, with minimal locking.
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 解析构建 如果解析不到内容会返回一个内部的 InjectionMetadata.EMPTY 空对象
                metadata = buildAutowiringMetadata(clazz);
                // 放进缓存
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}

AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata

核心方法就是 buildAutowiringMetadata,进行分析我们进去看看:

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
    /**
     * 如果没有 Autowired Value 注解信息就返回 EMPTY
     * this.autowiredAnnotationTypes.add(Autowired.class);
     * this.autowiredAnnotationTypes.add(Value.class);
     */
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    }
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;
    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
        // 遍历Class中的所有field,根据注解判断每个field是否需要被注入
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            // 看看field是不是有注解@Autowired 或 @Value
            MergedAnnotation<?> ann = findAutowiredAnnotation(field);
            if (ann != null) {
                // 不支持静态类
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                // 确定带注解的字段是否存在required并且是true 默认是true
                boolean required = determineRequiredStatus(ann);
                // AutowiredFieldElement 对象包装一下
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
        // 遍历Class中的所有method,根据注解判断每个method是否需要注入
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 桥接方法 什么是桥接方法 大概查了查跟泛型方法有关系
            // 我猜的哈 比如某一个泛型方法 没有具体的实现的话 不知道注入何种类型 就会略过吧  知道的还请告知哈
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            // 看看方法是不是有注解@Autowired 或 @Value
            MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                // 静态方法略过
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                // 参数为空的方法略过
                if (method.getParameterCount() == 0) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " +
                                method);
                    }
                }
                // 判断是不是有 required
                boolean required = determineRequiredStatus(ann);
                // 获取目标class中某成员拥有读或写方法与桥接方法一致的PropertyDescriptor
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                // AutowiredMethodElement 对象包装一下
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
        elements.addAll(0, currElements);
        // 递归调用
        targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);
    // 包装成 InjectionMetadata 对象  targetClass属性就是当前的类   injectedElements属性就是分析的字段或者方法
    return InjectionMetadata.forElements(elements, clazz);
}

可以看到会对类的方法的属性进行遍历以及父亲的递归,对于字段会忽略掉static修饰的,对于方法会也会忽略掉static以及参数为空的。

最后解析到的属性会包装成 AutowiredFieldElement ,方法会包装成 AutowiredMethodElement ,最后统一放进集合中,包装成 InjectionMetadata 对象返回,并放进缓存。

我们拿个例子

@Value("${user.list}")
private List<Integer> list;
private static String name;

@Value("${user.kuku}")
public void setName(String name) {
    this.name = name;
}

@Value 注入

AutowiredAnnotationBeanPostProcessor#postProcessProperties

  • 这里针对上述的例子进行注入,我们看看:
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 从上一步解析的缓存中直接获取
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // 进行注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}

InjectionMetadata#inject

// 注入
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) {
            // 调用对象对应的方法进行注入 属性的就是 AutowiredFieldElement  方法的就是 AutowiredMethodElement
            element.inject(target, beanName, pvs);
        }
    }
}

AutowiredFieldElement#inject 属性注入

可以看到最后 element.inject 就是在解析阶段调用对应的注入方法进行注入。

  • inject
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Field field = (Field) this.member;
    Object value;
    // 默认是 false 跳过
    if (this.cached) {
        try {
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Unexpected removal of target bean for cached argument -> re-resolve
            value = resolveFieldValue(field, bean, beanName);
        }
    }
    else {
        // 解析获取属性值
        value = resolveFieldValue(field, bean, beanName);
    }
    // 不为空的就通过反射设置进去值
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
    }
}
  • resolveFieldValue
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
    // 构造 DependencyDescriptor 依赖描述
    DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
    desc.setContainingClass(bean.getClass());
    Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
    Assert.state(beanFactory != null, "No BeanFactory available");
    // 获取 TypeConverter 类型转换器
    TypeConverter typeConverter = beanFactory.getTypeConverter();
    Object value;
    try {
        // 委托给 beanFactory.resolveDependency 方法获取
        value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
    }
    catch (BeansException ex) {
        throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
    }
    ...
}
  • resolveDependency
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    // 参数名称解析器
    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    // 此处是针对不同的类型对返回值进行处理,但核心逻辑依然到 doResolveDependency 方法
    if (Optional.class == descriptor.getDependencyType()) {
        return createOptionalDependency(descriptor, requestingBeanName);
    }
    else if (ObjectFactory.class == descriptor.getDependencyType() ||
            ObjectProvider.class == descriptor.getDependencyType()) {
        return new DependencyObjectProvider(descriptor, requestingBeanName);
    }
    else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
        return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    }
    // 此分支才是我们最常用的,核心逻辑在 doResolveDependency
    else {
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
                descriptor, requestingBeanName);
        if (result == null) {
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}
  • doResolveDependency
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
    try {
        // ShortcutDependencyDescriptor 处理,即缓存处理
        Object shortcut = descriptor.resolveShortcut(this);
        if (shortcut != null) {
            return shortcut;
        }
        Class<?> type = descriptor.getDependencyType();
        // 如果是 @Value 注解元素,则获取 value 值
        Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
        if (value != null) {
            if (value instanceof String) {
                // 占位符解析
                String strVal = resolveEmbeddedValue((String) value);
                BeanDefinition bd = (beanName != null && containsBean(beanName) ?
                        getMergedBeanDefinition(beanName) : null);
                // SpEL 解析
                value = evaluateBeanDefinitionString(strVal, bd);
            }
            // 类型转换后返回
            TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
            try {
                return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
            }
            catch (UnsupportedOperationException ex) {
                // A custom TypeConverter which does not support TypeDescriptor resolution...
                return (descriptor.getField() != null ?
                        converter.convertIfNecessary(value, type, descriptor.getField()) :
                        converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
            }
        }
   }
   ...

}
  • 关键的方法:resolveEmbeddedValue,由于方法嵌套调用过多哈,我们这里直接就不深究了哈:

  • 小结 : AutowiredMethodElement# inject 方法注入

针对方法实际就是调用的AutowiredMethodElement 的注入方法,这里就不看了哈,(主要是内容太多还涉及到依赖很多细节)我们看看调试:

X 参考文献

posted @ 2024-10-15 15:28  千千寰宇  阅读(122)  评论(0编辑  收藏  举报