前言
@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(")");}@Overridepublic 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;@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}@Overridepublic 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方法,查看运行结果