前言
@Value注解在Spring的依赖注入中占据重要地位,这里对@Value注解的作用进行演示以及扩展
作用
- 注入字符串
 - 注入属性
 - 注入bean
 - 其他
 
代码准备
创建两个普通的bean
@Component
public class ValueComponent {
} 
@Component
public class Foo {
    private String sign;
    public Foo() {
        this.sign = UUID.randomUUID().toString().replaceAll("-", "");
    }
    public String getSign() {
        return sign;
    }
    public void setSign(String sign) {
        this.sign = sign;
    }
} 
创建配置文件val.properties
key=source
source=spring
color=blank,white,red 
创建配置类
@ComponentScan("com.test.val")
@PropertySource("classpath:val.properties")
public class AppConfig {
}
 
创建启动类
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    }
} 
示例
注入字符串
@Component
public class ValueComponent {
    @Value("hello world")
    private String helloWorld;
} 

注入属性
注入普通属性
@Component
public class ValueComponent {
    @Value("${key}")
    private String key;
} 

注入嵌套属性
@Component
public class ValueComponent {
    @Value("${${key}}")
    private String nestKey;
} 

注入的属性不存在
@Component
public class ValueComponent {
    @Value("${server.port}")
    private String absentKey;
} 

PS : Spring默认情况下使用的是宽松模式, 解析不了的属性等于注入了字符串
注入的属性不存在,使用默认值
@Component
public class ValueComponent {
    @Value("${server.port:8080}")
    private String absentDefaultKey;
} 

注入bean及其属性
@Component
public class ValueComponent {
    @Value("#{foo}")
    private Foo foo;
    @Value("#{foo['sign']}")
    private String sign;
} 

其他
@Component
public class ValueComponent {
    @Value("https://www.baidu.com/")
    private URL url;
    @Value("classpath:val.properties")
    private Resource resource;
} 

属性注入优先级问题
创建配置文件val2.properties
key=source2
source=spring2 
修改配置文件
@ComponentScan("com.test.val")
@PropertySources({@PropertySource("classpath:val.properties"), @PropertySource("classpath:val2.properties")})
public class AppConfig {
} 
注入普通属性key
@Component
public class ValueComponent {
    @Value("${key}")
    private String key;
} 

Spring默认情况下创建的Environment是StandardEnvironment,会添加两个默认PropertySource : systemProperties systemEnvironment
系统默认添加的两个PropertySource优先级最高,使用@PropertySource(@PropertySources)注解导入的propertySource,越先解析优先级越低
当前环境的PropertySource排序
systemProperties > systemEnvironment > val2.properties > val1.properties
如果在优先级较高的PropertySource里面找到了相关属性,则直接返回不会查找优先级较低的PropertySource了

Springboot对Spring做了很多扩展, 存在很多PropertySource

对@Value属性注入的扩展
如果beanFactory中不存在embeddedValueResolvers则会添加一个默认的embeddedValueResolvers
AbstractApplicationContext#finishBeanFactoryInitialization

DefaultListableBeanFactory#doResolveDependency

AbstractBeanFactory#resolveEmbeddedValue

在上述的前提下我们可以自定义一个StringValueResolver来解析@Value注解传入的字符串
创建MergedResolver对象
public class MergedResolver implements StringValueResolver {
    private PropertySources propertySources;
    private final PropertySourcesPropertyResolver defaultResolver;
    private final PropertySourcesPropertyResolver resolver1;
    private final PropertySourcesPropertyResolver resolver2;
    public MergedResolver(PropertySources propertySources) {
        this.propertySources = propertySources;
        defaultResolver = new PropertySourcesPropertyResolver(this.propertySources);
        resolver1 = new PropertySourcesPropertyResolver(this.propertySources);
        resolver1.setPlaceholderPrefix("$[");
        resolver1.setPlaceholderSuffix("]");
        resolver2 = new PropertySourcesPropertyResolver(this.propertySources);
        resolver2.setPlaceholderPrefix("$(");
        resolver2.setPlaceholderSuffix(")");
    }
    @Override
    public String resolveStringValue(String strVal) {
        if (strVal.startsWith("$[")) {
            return resolver1.resolvePlaceholders(strVal);
        } else if (strVal.startsWith("$(")) {
            return resolver2.resolvePlaceholders(strVal);
        } else {
            return defaultResolver.resolvePlaceholders(strVal);
        }
    }
} 
创建StringValueResolverImporter对象
public class StringValueResolverImporter implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    private Environment environment;
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) registry;
        // 添加自定义EmbeddedValueResolver
        // 自定义的EmbeddedValueResolver要兼容默认的EmbeddedValueResolver,否则默认的@Value功能全部失效
        // 一定要兼容默认的EmbeddedValueResolver 一定要兼容默认的EmbeddedValueResolver 一定要兼容默认的EmbeddedValueResolver
        // 重要的事情说三遍 ! ! !
        beanFactory.addEmbeddedValueResolver(new MergedResolver(((StandardEnvironment) environment).getPropertySources()));
    }
} 
修改配置文件
@ComponentScan("com.test.val")
@Import(StringValueResolverImporter.class)
@PropertySources({@PropertySource("classpath:val.properties"), @PropertySource("classpath:val2.properties")})
public class AppConfig {
} 
修改ValueComponent
@Component
public class ValueComponent {
    @Value("${key}")
    private String key1;
    @Value("$[key]")
    private String key2;
    @Value("$(key)")
    private String key3;
} 
运行Main方法,查看运行结果

Springboot对@Value类型转换的扩展
修改ValueComponent
@Component
public class ValueComponent {
    @Value("${color}")
    private List<String> color;
} 

如果使用的是SpringBoot,会将字符串以逗号分割,然后放入list中

主要原因是Springboot给BeanFactory添加了一个ApplicationConversionService,这个类的默认构造方法会添加很多convert





通过源码我们知道了这个扩展点可以使用@Delimiter指定分隔符,然后默认分隔符是逗号
使用Spring达到同样效果
复用StringValueResolverImporter代码

修改val.properties
key=source
source=spring
color=blank,white,red
car=redCar;whiteCar;blackCar 
修改ValueComponent
@Component
public class ValueComponent {
    @Value("${color}")
    private List<String> color;
    @Value("${car}")
    @Delimiter(";")
    private List<String> car;
}
 
运行Main方法,查看运行结果




















