前言
@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方法,查看运行结果