Spring @Value 属性注入使用总结一

参考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的用法

 
  1. import org.springframework.stereotype.Service;

  2.  
  3. /**

  4. * 测试Bean

  5. */

  6. @Service("userService")

  7. public class UserService {

  8.  
  9. public int count() {

  10. return 10;

  11. }

  12.  
  13. public int max(int size) {

  14. int count = count();

  15. return count > size ? count : size;

  16. }

  17. }

 
  1. import org.springframework.beans.factory.InitializingBean;

  2. import org.springframework.beans.factory.annotation.Value;

  3. import org.springframework.stereotype.Component;

  4.  
  5. @Component

  6. public class AppRunner implements InitializingBean {

  7.  
  8. /**

  9. * 引用一个配置项

  10. */

  11. @Value("${app.port}")

  12. private int port;

  13.  
  14. /**

  15. * 调用容器的一个bean的方法获取值

  16. */

  17. @Value("#{userService.count()}")

  18. private int userCount;

  19.  
  20. /**

  21. * 调用容器的一个bean的方法,且传入一个配置项的值作为参数

  22. */

  23. @Value("#{userService.max(${app.size})}")

  24. private int max;

  25.  
  26. /**

  27. * 简单的运算

  28. */

  29. @Value("#{${app.size} <= '12345'.length() ? ${app.size} : '12345'.length()}")

  30. private int min;

  31.  
  32. //测试

  33. public void afterPropertiesSet() throws Exception {

  34. System.out.println("port : " + port);

  35. System.out.println("userCount : " + userCount);

  36. System.out.println("max : " + max);

  37. System.out.println("min : " + min);

  38. }

  39. }


 

app.properties

app.port=9090

app.size=3
 
  1. import org.springframework.context.annotation.AnnotationConfigApplicationContext;

  2. import org.springframework.context.annotation.ComponentScan;

  3. import org.springframework.context.annotation.PropertySource;

  4.  
  5. @ComponentScan

  6. @PropertySource("classpath:app.properties")

  7. public class App {

  8.  
  9. public static void main( String[] args) {

  10. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(App.class);

  11. context.close();

  12. }

  13. }


运行,输出结果

port : 9090
userCount : 10
max : 10
min : 3

一般的用法就是这样,用于注入一个值。

那么,能否做到,我给定一个表达式或者具体的值,它能帮忙计算出表达式的值呢? 也就是说,实现一个@Value的功能呢?

方法如下:

 
  1. import org.springframework.beans.factory.config.BeanExpressionContext;

  2. import org.springframework.beans.factory.config.BeanExpressionResolver;

  3. import org.springframework.beans.factory.config.ConfigurableBeanFactory;

  4. import org.springframework.context.expression.StandardBeanExpressionResolver;

  5.  
  6. public class ValueUtil {

  7.  
  8. private static final BeanExpressionResolver resolver = new StandardBeanExpressionResolver();

  9.  
  10. /**

  11. * 解析一个表达式,获取一个值

  12. * @param beanFactory

  13. * @param value 一个固定值或一个表达式。如果是一个固定值,则直接返回固定值,否则解析一个表达式,返回解析后的值

  14. * @return

  15. */

  16. public static Object resolveExpression(ConfigurableBeanFactory beanFactory, String value) {

  17. String resolvedValue = beanFactory.resolveEmbeddedValue(value);

  18.  
  19. if (!(resolvedValue.startsWith("#{") && value.endsWith("}"))) {

  20. return resolvedValue;

  21. }

  22.  
  23. return resolver.evaluate(resolvedValue, new BeanExpressionContext(beanFactory, null));

  24. }

  25. }


具体使用如下:

 
  1. import org.springframework.context.annotation.AnnotationConfigApplicationContext;

  2. import org.springframework.context.annotation.ComponentScan;

  3. import org.springframework.context.annotation.PropertySource;

  4.  
  5. @ComponentScan

  6. @PropertySource("classpath:app.properties")

  7. public class App {

  8.  
  9. public static void main( String[] args) {

  10. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(App.class);

  11. //计算一个具体的值(非表达式)

  12. System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "1121"));

  13. //实现@Value的功能

  14. System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "${app.port}"));

  15. System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "#{userService.count()}"));

  16. System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "#{userService.max(${app.size})}"));

  17. System.out.println(ValueUtil.resolveExpression(context.getBeanFactory(), "#{${app.size} <= '12345'.length() ? ${app.size} : '12345'.length()}"));

  18. context.close();

  19. }

  20. }


运行输出如下:

1121
9090
10
10
3

发现已经实现了@Value的功能

最后,可能有人就有疑问了,这有什么用呢?我直接用@Value难道不好吗?

对于大部分场景下,的确直接用@Value就可以了。但是,有些特殊的场景,@Value做不了

比如说,我们定义一个注解

 
  1. @Retention(RUNTIME)

  2. @Target(TYPE)

  3. public @interface Job {

  4. String cron();

  5. }

这个注解需要一个cron的表达式,我们的需求是,使用方可以直接用一个cron表达式,也可以支持引用一个配置项(把值配置到配置文件中)

比如说

@Job(cron = "0 0 12 * * ?")

@Job(cron = "${app.job.cron}")
 

这种情况@Value就做不到,但是,可以用我上面的解决方案。

猜你喜欢

转载自blog.csdn.net/Michaelwubo/article/details/81289330
今日推荐