参考https://blog.csdn.net/hry2015/article/details/72353994
https://blog.csdn.net/qq_17586821/article/details/79802320
spring boot允许我们把配置信息外部化。由此,我们就可以在不同的环境中使用同一套程序代码。可以使用属性文件,yaml文件,环境变量,命令行参数来实现配置信息的外部化。可以使用@Value注解来将属性值直接注入到bean里边。也可以使用@ConfigurationProperties注解将属性值注入到结构化的对象里边。
- 配置文件 application-dev.properties
-
# @Value 测试 dev.value.name=Tom dev.value.age=20
- 定义bean
-
public class TeeProperties { private static final Logger logger = LoggerFactory.getLogger(TeeProperties.class); @Value("${dev.value.name}") private String name; @Value("${dev.value.age}") private Integer age; @PostConstruct public void valueInjectTest() { logger.info("使用 @Value 和 profile 文件的结合实现属性值的注入:name = {}, age = {}", name, age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
从上面可以看到,我们使用@Value注解将配置文件中的配置信息注入到bean的属性中。并且,为了验证是否成功地注入了配置信息,我们使用日志打印了配置信息,下面我们通过日志记录来验证一下。
- 查看日志,验证是否成功注入配置信息
-
Tip: 个人觉得,在写测试用例的时候,使用@Value进行单个属性注入是非常方便的。
当然,如果说一个bean有好多属性或者这些属性之间有继承关系的话,我们还用@Value来一个一个注入,就显得有点麻烦了。其实 spring boot 还提供了另外一个注解 @ConfigurationProperties 来解决这个问题,该注解的具体使用方法,下一篇博客会讲。https://blog.csdn.net/Michaelwubo/article/details/81289504
@Value注入
不通过配置文件的注入属性的情况
通过@Value将外部的值动态注入到Bean中,使用的情况有:
- 注入普通字符串
- 注入操作系统属性
- 注入表达式结果
- 注入其他Bean属性:注入beanInject对象的属性another
- 注入文件资源
-
注入URL资源
详细代码见:
BaseValueInject.java
@Component
public class BaseValueInject {
@Value("normal")
private String normal; // 注入普通字符串
@Value("#{systemProperties['os.name']}")
private String systemPropertiesName; // 注入操作系统属性
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber; //注入表达式结果
@Value("#{beanInject.another}")
private String fromAnotherBean; // 注入其他Bean属性:注入beanInject对象的属性another,类具体定义见下面
@Value("classpath:config.txt")
private Resource resourceFile; // 注入文件资源
@Value("http://www.baidu.com")
private Resource testUrl; // 注入URL资源
@Override
public String toString() {
return "BaseValueInject{" +
"normal='" + normal + '\'' +
", systemPropertiesName='" + systemPropertiesName + '\'' +
", randomNumber=" + randomNumber +
", fromAnotherBean='" + fromAnotherBean + '\'' +
", resourceFile=" + resourceFile +
", testUrl=" + testUrl +
'}';
}
}
注入其他Bean属性:注入beanInject对象的属性another
@Component(value = "beanInject")
public class BeanInject {
@Value("wuqi")
private String another;
public String getAnother() {
return another;
}
public void setAnother(String another) {
this.another = another;
}
}
注入文件资源:com/hry/spring/configinject/config.txt
注意maven项目一定是在resource目录下
获取当前类的跟路径this.getClass().getResource("/").toURI().toString()
System.getProperty("java.class.path").toString()/当先系统的java path
System.getProperty("user.dir").toStrng()//用户项目的根目录。如:D:/springcloud
test configuration file
测试类:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes=Application.class)
public class ConfiginjectApplicationTest {
@Autowired
private BaseValueInject baseValueInject;
@Test
public void baseValueInject(){
System.out.println(baseValueInject.toString());
}
}
运行测试类
normal=normal
systemPropertiesName=Windows 10
randomNumber=35.10603794922444
fromAnotherBean=其他Bean的属性
resourceFile=test configuration file
testUrl=<html>...<title>百度一下,你就知道</title>...略</html>
===========
通过配置文件的注入属性的情况
通过@Value将外部配置文件的值动态注入到Bean中。配置文件主要有两类:
- application.properties。application.properties在spring boot启动时默认加载此文件
- 自定义属性文件。自定义属性文件通过@PropertySource加载。@PropertySource可以同时加载多个文件,也可以加载单个文件。如果相同第一个属性文件和第二属性文件存在相同key,则最后一个属性文件里的key启作用。加载文件的路径也可以配置变量,如下文的${anotherfile.configinject},此值定义在第一个属性文件config.properties
第一个属性文件config.properties内容如下:
${anotherfile.configinject}作为第二个属性文件加载路径的变量值
book.name=bookName
anotherfile.configinject=placeholder
第二个属性文件config_placeholder.properties内容如下:
book.name.placeholder=bookNamePlaceholder
下面通过@Value(“${app.name}”)语法将属性文件的值注入bean属性值,详细代码见:
@Component(value = "configurationFileInject")
@ConfigurationProperties(prefix = "spring.datasource.shareniu")
@PropertySource(value = {"classpath:config.properties","classpath:config_${anotherfile.configinject}.properties","classpath:jdbc.properties"},ignoreResourceNotFound = false,encoding = "UTF-8")
public class ConfigurationFileInject {
@Value("${app.name}")
private String appName; // 这里的值来自application.properties,spring boot启动时默认加载此文件
@Value("${book.name}")
private String bookName; // 注入第一个配置外部文件属性
@Value("${book.name.placeholder}")
private String bookNamePlaceholder; // 注入第二个配置外部文件属性
//@Value("${spring.datasource.shareniu.url}")
private String url; // 注入第二个配置外部文件属性
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Autowired
private Environment env; // 注入环境变量对象,存储注入的属性值
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("bookName=").append(bookName).append("\r\n")
.append("bookNamePlaceholder=").append(bookNamePlaceholder).append("\r\n")
.append("appName=").append(appName).append("\r\n")
.append("env=").append(env).append("\r\n")
// 从eniroment中获取属性值
.append("env=").append(env.getProperty("book.name.placeholder")).append("\r\n");
return sb.append("url=").append(url).toString();
}
}
@ConfigurationProperties
@PropertySource
的使用原理之类的见一下连接
https://blog.csdn.net/Michaelwubo/article/details/81289504
测试代码:
Application.java同上文
@RunWith(SpringRunner.class)
@SpringBootTest(classes=Application.class)
public class ConfiginjectApplicationTest {
@Autowired
private ConfigurationFileInject configurationFileInject;
@Test
public void configurationFileInject(){
System.out.println(configurationFileInject.toString());
}
}
测试运行结果:
bookName=bookName
bookNamePlaceholder=bookNamePlaceholder
appName=appName
env=StandardEnvironment {activeProfiles=[], defaultProfiles=[default], propertySources=[Inlined Test Properties,systemProperties,systemEnvironment,random,applicationConfig: [classpath:/application.properties],class path resource [com/hry/spring/configinject/config_placeholder.properties],class path resource [com/hry/spring/configinject/config.properties]]}
env=bookNamePlaceholder
1. @Value注入二 参考https://blog.csdn.net/hry2015/article/details/72453920
在上一篇文章中Spring @Value 属性注入使用总结一我们介绍了@Value的常用方式。看完文章你可能迷惑#{..}和${}有什么区别以及如何使用。这篇文章,我们尝试解决这个问题
1.1 前提
测试属性文件:advance_value_inject.properties
server.name=server1,server2,server3
#spelDefault.value=notdefault
HelloWorld_=sss
测试类AdvanceValueInject:引入advance_value_inject.properties文件,作为属性的注入
@Component
@PropertySource({"classpath:com/hry/spring/configinject/advance_value_inject.properties"})
public class AdvanceValueInject {
...
}
1.2 #{…}和${…}
${…}用法
{}里面的内容必须符合SpEL表达式,详细的语法,以后可以专门开新的文章介绍, 通过@Value(“${spelDefault.value}”)可以获取属性文件中对应的值,但是如果属性文件中没有这个属性,则会报错。可以通过赋予默认值解决这个问题,如@Value("${spelDefault.value:127.0.0.1}")
详细代码如下:
// 如果属性文件没有spelDefault.value,则会报错
// @Value("${spelDefault.value}")
// private String spelDefault2;
// 使用default.value设置值,如果不存在则使用默认值
@Value("${spelDefault.value:127.0.0.1}")
private String spelDefault;
#{…}用法
这里只演示简单用法:
// SpEL:调用字符串Hello World的concat方法
@Value("#{'Hello World'.concat('!')}")
private String helloWorld;
// SpEL: 调用字符串的getBytes方法,然后调用length属性
@Value("#{'Hello World'.bytes.length}")
private String helloWorldbytes;
${…}和#{…}混合使用 ${...}和#{...}
可以混合使用,如下文代码执行顺序:通过${server.name}从属性文件中获取值并进行替换,然后就变成了 执行SpEL表达式{‘server1,server2,server3’.split(‘,’)}。
// SpEL: 传入一个字符串,根据","切分后插入列表中, #{}和${}配置使用(注意单引号,注意不能反过来${}在外面,#{}在里面)
@Value("#{'${server.name}'.split(',')}")
private List<String> servers;
在上文中在#{}外面,${}在里面
可以执行成功,那么反过来是否可以呢${}在外面,#{}在里面
,如代码
// SpEL: 注意不能反过来${}在外面,#{}在里面,这个会执行失败
@Value("${#{'HelloWorld'.concat('_')}}")
private List<String> servers2;
答案是不能。因为spring执行${}是时机要早于#{}。在本例中,Spring会尝试从属性中查找#{‘HelloWorld’.concat(‘_’)},那么肯定找到,由上文已知如果找不到,然后报错。所以${}在外面,#{}在里面
是非法操作
小结
- #{…} 用于执行SpEl表达式,并将内容赋值给属性
- ${…} 主要用于加载外部属性文件中的值
- #{…} 和${…} 可以混合使用,但是必须
#{}外面,${}在里面
=======================================================
本文章使用的Spring版本4.3.10.RELEASE
https://blog.csdn.net/mn960mn/article/details/77430685
@Value在Spring中,功能非常强大,可以注入一个配置项,可以引用容器中的Bean(调用其方法),也可以做一些简单的运算
如下的一个简单demo,演示@Value的用法
-
import org.springframework.stereotype.Service;
-
/**
-
* 测试Bean
-
*/
-
@Service("userService")
-
public class UserService {
-
public int count() {
-
return 10;
-
}
-
public int max(int size) {
-
int count = count();
-
return count > size ? count : size;
-
}
-
}
-
import org.springframework.beans.factory.InitializingBean;
-
import org.springframework.beans.factory.annotation.Value;
-
import org.springframework.stereotype.Component;
-
@Component
-
public class AppRunner implements InitializingBean {
-
/**
-
* 引用一个配置项
-
*/
-
@Value("${app.port}")
-
private int port;
-
/**
-
* 调用容器的一个bean的方法获取值
-
*/
-
@Value("#{userService.count()}")
-
private int userCount;
-
/**
-
* 调用容器的一个bean的方法,且传入一个配置项的值作为参数
-
*/
-
@Value("#{userService.max(${app.size})}")
-
private int max;
-
/**
-
* 简单的运算
-
*/
-
@Value("#{${app.size} <= '12345'.length() ? ${app.size} : '12345'.length()}")
-
private int min;
-
//测试
-
public void afterPropertiesSet() throws Exception {
-
System.out.println("port : " + port);
-
System.out.println("userCount : " + userCount);
-
System.out.println("max : " + max);
-
System.out.println("min : " + min);
-
}
-
}
app.properties
app.port=9090
app.size=3
-
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-
import org.springframework.context.annotation.ComponentScan;
-
import org.springframework.context.annotation.PropertySource;
-
@ComponentScan
-
@PropertySource("classpath:app.properties")
-
public class App {
-
public static void main( String[] args) {
-
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(App.class);
-
context.close();
-
}
-
}
运行,输出结果
port : 9090
userCount : 10
max : 10
min : 3
一般的用法就是这样,用于注入一个值。
那么,能否做到,我给定一个表达式或者具体的值,它能帮忙计算出表达式的值呢? 也就是说,实现一个@Value的功能呢?
方法如下:
-
import org.springframework.beans.factory.config.BeanExpressionContext;
-
import org.springframework.beans.factory.config.BeanExpressionResolver;
-
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-
import org.springframework.context.expression.StandardBeanExpressionResolver;
-
public class ValueUtil {
-
private static final BeanExpressionResolver resolver = new StandardBeanExpressionResolver();
-
/**
-
* 解析一个表达式,获取一个值
-
* @param beanFactory
-
* @param value 一个固定值或一个表达式。如果是一个固定值,则直接返回固定值,否则解析一个表达式,返回解析后的值
-
* @return
-
*/
-
public static Object resolveExpression(ConfigurableBeanFactory beanFactory, String value) {
-
String resolvedValue = beanFactory.resolveEmbeddedValue(value);
-
if (!(resolvedValue.startsWith("#{") && value.endsWith("}"))) {
-
return resolvedValue;
-
}
-
return resolver.evaluate(resolvedValue, new BeanExpressionContext(beanFactory, null));
-
}
-
}
具体使用如下:
-
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-
import org.springframework.context.annotation.ComponentScan;
-
import org.springframework.context.annotation.PropertySource;
-
@ComponentScan
-
@PropertySource("classpath:app.properties")
-
public class App {
-
public static void main( String[] args) {
-
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(App.class);
-
//计算一个具体的值(非表达式)
-
System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "1121"));
-
//实现@Value的功能
-
System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "${app.port}"));
-
System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "#{userService.count()}"));
-
System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "#{userService.max(${app.size})}"));
-
System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "#{${app.size} <= '12345'.length() ? ${app.size} : '12345'.length()}"));
-
context.close();
-
}
-
}
运行输出如下:
1121
9090
10
10
3
发现已经实现了@Value的功能
最后,可能有人就有疑问了,这有什么用呢?我直接用@Value难道不好吗?
对于大部分场景下,的确直接用@Value就可以了。但是,有些特殊的场景,@Value做不了
比如说,我们定义一个注解
-
@Retention(RUNTIME)
-
@Target(TYPE)
-
public @interface Job {
-
String cron();
-
}
这个注解需要一个cron的表达式,我们的需求是,使用方可以直接用一个cron表达式,也可以支持引用一个配置项(把值配置到配置文件中)
比如说
@Job(cron = "0 0 12 * * ?")
@Job(cron = "${app.job.cron}")
这种情况@Value就做不到,但是,可以用我上面的解决方案。