• @Value的用法


    @Value属于spring的注解,在spring-beans包下,可以在 字段 或 方法参数 或 构造函数参数 上使用,通常用于属性注入。支持SpEL (Spring Expression Language)表达式来注入值,同时也支持属性占位符注入值。

    篇幅较长,相对来说写的非常细了,基本上涉及到的面全有了,建议收藏慢慢看!

    一、属性注入三种方式

    使用@Value前提:

    1. 不能直接作用于静态变量(static);
    2. 不能直接作用于常量(final);
    3. 不能在非注册的类中使用(类需要被注册在spring上下文中,如用@Service,@RestController,@Component等);
    4. 使用这个类时,只能通过依赖注入的方式,用new的方式是不会自动注入这些配置的。

    1.1.配置文件读取值

    @Value(“${xxxx}”)注解从配置文件读取值的用法,也就是从application.yml / application.properties文件中获取值。

    @Configuration
    public class MyConfig {
    
        @Value("${spring.application.name}")
        private String name;
    
        public String getName() {
            return name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    application.yml

    spring:
      application:
        name: test
    
    • 1
    • 2
    • 3

    成功获取到application当中的值。

    在这里插入图片描述

    倘若application当中没有配置spring.application.name这时候启动项目就报异常了。

    在这里插入图片描述

    我们可以这样写:@Value("${spring.application.name:test111}") 代表的是假如application当中读不到值,那么就使用test111。这样可以完全避免没有设置值而启动报错的问题。当然也可以不设置默认值,比如@Value("${spring.application.name:}") 这样同样可以避免没有设置值启动报错的问题!

    1.2.直接赋值

    @Value("张三")属性注入有点类似于直接给属性赋值一样,但是实际开发当中这种应用场景非常少。

    @Configuration
    public class MyConfig {
    
        @Value("张三")
        private String name;
    
        public String getName() {
            return name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.3.SpEL 表达式注入

    @Value(“#{xxxx}”)SpEL表达式的形式。想要深入了解SpEL表达式的可以看这一篇文章:https://blog.csdn.net/weixin_43888891/article/details/127520555

    JAVA获得系统配置文件的System Properties,其中里面有一个属性就是user.language===zh,其实在spring当中我们可以通过SpEL 表达式来获取System Properties当中的属性值。

    public class SystemProperties {
        public static void main(String[] args) {
            Properties properties = System.getProperties();
            Iterator<Map.Entry<Object, Object>> iterator = properties.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Object, Object> entry = iterator.next();
                System.out.println(entry.getKey() + "===" + entry.getValue());
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    systemProperties是spring预定义的,我们可以拿来直接用的。

    @Value("#{systemProperties['user.language']}")
    
    • 1

    二、property标签

    @Value根本不是通过属性的set方法进行注入的,这个我也是亲自做了试验。@Value注解并不等于xml当中property标签,property标签是利用set方法来赋值的。并且property 当中的value属性也可以使用SpEL表达式。

    <bean id="dog1" class="com.gzl.cn.springbootcache.config.Student">
         <property name="name" value="#{systemProperties['user.language']}"/>
         <property name="age" value="27"/>
     bean>
    
    • 1
    • 2
    • 3
    • 4

    三、构造函数注入

    如果需要读取配置文件application.yml 的属性值,只需要在变量上加 @Value("${属性名}") 注解,就可以将配置文件 application.yml 的一个属性值赋值给变量。但是,如果我们在对象的构造方法中使用这个变量,结果发现这个变量的值为null。

    @Configuration
    public class MyConfig {
    
        @Value("${spring.application.name}")
        private String name;
    
        public MyConfig() {
            System.out.println(name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    原因很简单构造器优先于属性注入,以至于属性还没注入进去,构造器当然拿不到值了。 那么在构造方法中如果要使用配置文件中的属性值,该怎么使用呢?见下方代码:

    @Configuration
    public class MyConfig {
    
        @Value("${spring.application.name}")
        private String name;
    
        public String getName() {
            return name;
        }
    
        public MyConfig(@Value("${spring.application.name}") String name1) {
            System.out.println(name1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    四、方法注入

    方法参数注入

    @Configuration
    public class MyConfig {
    
        @Bean
        public Student getName(@Value("${spring.application.name}")String name) {
            Student student = new Student();
            student.setName(name);
            return student;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方法上注入,方法注入就是在方法上使用@Value,然后在注入到容器的过程当中他会读取@Value的值,然后将值传参访问这个方法。

    @Configuration
    public class MyConfig {
    
        private String name;
    
        @Value("${spring.application.name}")
        public void setName(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如果属性上使用了@Value然后set方法上也使用了@Value,那么这个属性最终会被赋值两次,保留下来的是set的值,因为set优先级比属性赋值要低。

    五、静态变量注入

    使用@Value注解是不允许在static变量注入的,包括get方法也是,直接会获取null值。原因很简单,@Value围绕的是注入到spring容器当中的这个单例对象,而static是类变量,所以肯定不可以的。可以理解为 类变量初始化优先于spring对象注入,所以他无法注入进去。

    @Configuration
    public class MyConfig {
    
        @Value("${spring.application.name}")
        private static String name;
    
        public static String getName() {
            return name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果就想使用静态变量怎么办,其实是有很多种方案可以实现的。

    5.1.通过方法注入

    @Configuration
    public class MyConfig {
    
        public static String name;
    
        @Value("${spring.application.name}")
        public void initName(String s) {
            name = s;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.2.通过@PostConstruct

    Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。

    Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
    
    • 1
    @Configuration
    public class MyConfig {
    
        public static String name;
    
        @Value("${spring.application.name}")
        private String s;
    
        @PostConstruct
        public void init(){
            name = s;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5.3.继承InitializingBean接口

    InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。

    @Configuration
    public class MyConfig implements InitializingBean {
    
        public static String name;
    
        @Value("${spring.application.name}")
        private String s;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            name = s;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    静态变量一般不建议设置为public,我们可以通过static 的 get方法来获取,因为静态变量设置为public,一旦有些地方没注意到把变量给改变值了,那这个值就彻底改变了,除非重启项目。

    六、给不同类型变量注入

    6.1.集合和数组

    test:
      array1: aaa,bbb,ccc
      array2: 111,222,333
    
    • 1
    • 2
    • 3

    使用@Value("${ }")是直接属性赋值,@Value("#{}")是使用的SpEL表达式。SpEL表达式读是先读出来字符串,然后我们可以再进行处理。使用SpEL读不到的话是null,而使用属性赋值的话是空数组。

    @Configuration
    public class MyConfig {
    
        // 数组
        @Value("${test.array1:}")
        private String[] array1;
    
        // 集合
        @Value("${test.array1:}")
        private List<String> list1;
    
        // 集合
        @Value("#{'${test.array2}'.split(',')}")
        private List<String> list2;
    
        // 集合进一步做空数据处理,读不到值是一个null
        @Value("#{'${test.list:}'.empty ? null : '${test.list:}'.split(',')}")
        private List<String> testList;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.2.map

    test:
      map1: '{"name": "zhangsan", "sex": "male"}' 
      map2: '{"math": "90", "english": "85"}'
    
    • 1
    • 2
    • 3
    @Value("#{${test.map2}}")
    private Map<String,String> map1;
    @Value("#{${test.map2}}")
    private Map<String,Integer> map2;
    
    • 1
    • 2
    • 3
    • 4

    6.3.文件或url资源

    @Value("classpath:com/gzl/spring/configinject/config.txt")
    private Resource resourceFile; // 注入文件资源
    
    @Value("http://www.baidu.com")
    private Resource testUrl; // 注入URL资源
    
    • 1
    • 2
    • 3
    • 4
    • 5

    七、大小写以及命名问题

    不要在application.yml/properties文件中使用驼峰命名。尽量用-分割。
    我看了一下原生框架的配置,发现人家确实没大小写。

    file:
      winUploadPath: D:/opt/tongue/uploadPath
    
    • 1
    • 2

    使用@Value("${file.win-upload-path}")照样可以读出winUploadPath的值。

    @Configuration
    public class MyConfig {
    
        @Value("${file.win-upload-path}")
        private String filePath;
    
        public String getFilePath() {
            return filePath;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果yml当中使用的win-upload-path,那么@value必须使用win-upload-path读,否则启动报错。

    八、配合@PropertySource使用

    springboot默认读取的都是application.yml,或者application.properties,但是有时候我们想把一些配置给独立起来,这时候可以采用@PropertySource。

    新建properties,yml文件也是可以的,

    在这里插入图片描述

    demo.name=huang
    demo.sex=1
    demo.type=demo
    
    • 1
    • 2
    • 3
    @Component
    @PropertySource(value = "demo.properties")
    public class ReadByPropertySourceAndValue {
    
        @Value("${demo.name}")
        private String name;
    
        @Value("${demo.sex}")
        private int sex;
    
        @Value("${demo.type}")
        private String type;
    
        @Override
        public String toString() {
            return "ReadByPropertySourceAndValue{" +
                    "name='" + name + '\'' +
                    ", sex=" + sex +
                    ", type='" + type + '\'' +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    @PropertySource 和 @ConfigurationProperties配合使用

    @Component
    @PropertySource(value = "demo.properties")
    @ConfigurationProperties(prefix = "demo")
    public class ReadByPropertySourceAndValue {
    
        private String name;
    
        private int sex;
    
        private String type;
    
        @Override
        public String toString() {
            return "ReadByPropertySourceAndValue{" +
                    "name='" + name + '\'' +
                    ", sex=" + sex +
                    ", type='" + type + '\'' +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    九、@Value源码

    代码当中并没有set方法,那他是如何赋值给属性值的呢?

    @Configuration
    public class MyConfig {
        @Value("${spring.application.name}")
        public String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    @Value实际上是通过org.springframework.beans.factory.config.BeanPostProcessor来执行的。当然BeanPostProcessor是个接口,AutowiredAnnotationBeanPostProcessor是BeanPostProcessor的一个实现类,然后AutowiredAnnotationBeanPostProcessor负责检查是否有这个注解的存在。

    在这里插入图片描述

    AutowireCandidateResolver 的getSuggestedValue方法负责获取注解的value值。AutowireCandidateResolver实际上是个接口,真正的getSuggestedValue方法是访问的QualifierAnnotationAutowireCandidateResolver类当中的。

    在这里插入图片描述

    DefaultListableBeanFactory当中的resolveEmbeddedValue方法,通过表达式得到真正的值。

    在这里插入图片描述

    最终得到值通过反射Field的set赋值

    在这里插入图片描述

    官网源码:https://github.com/spring-projects/spring-framework/blob/main/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Value.java

    写作不易,如果这篇文章有帮助到您,麻烦您给小编留个赞哈!

  • 相关阅读:
    Linux定时任务详解
    如何在Linux系统中搭建Zookeeper集群
    Python+Requests+Pytest+YAML+Allure实现接口自动化
    23届秋招内推‖恒生电子
    使用HTML制作静态网站作业——我的校园运动会(HTML+CSS)
    【论文阅读】Distilling the Knowledge in a Neural Network
    移动端布局方案
    数据库的备份和恢复
    2022最新JUC+多线程面试题
    java2基础语法-运算符
  • 原文地址:https://blog.csdn.net/weixin_43888891/article/details/127505506