【Spring注解驅動開發】如何使用@Value注解為bean的屬性賦值,我們一起吊打面試官!


寫在前面

在之前的文章中,我們探討了如何向Spring的IOC容器中注冊bean組件,講解了有關bean組件的生命周期的知識。今天,我們就來一起聊聊@Value注解的用法。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

@Value注解

Spring中的@Value注解可以為bean中的屬性賦值。我們先來看看@Value注解的源碼,如下所示。

package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
	String value();
}

從@Value注解的源碼,我們可以看出:@Value注解可以標注在字段、方法、參數、注解上,在程序運行期間生效。

@Value注解用法

1.不通過配置文件注入屬性的情況

通過@Value將外部的值動態注入到Bean中,使用的情況有:

  • 注入普通字符串
@Value("normal")
private String normal; // 注入普通字符串
  • 注入操作系統屬性
@Value("#{systemProperties['os.name']}")
private String systemPropertiesName; // 注入操作系統屬性
  • 注入表達式結果
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber; //注入表達式結果
  • 注入其他Bean屬性
@Value("#{person.name}")
private String name; // 注入其他Bean屬性:注入person對象的屬性name
  • 注入文件資源
@Value("classpath:io/mykit/spring/config/config.properties")
private Resource resourceFile; // 注入文件資源
  • 注入URL資源
@Value("http://www.baidu.com")
private Resource url; // 注入URL資源

2.通過配置文件注入屬性的情況

通過@Value(“${app.name}”)語法將屬性文件的值注入到bean的屬性中,如下所示。

@Component
// 引入外部配置文件組:${app.configinject}的值來自config.properties。
// 如果相同
@PropertySource({"classpath:io/mykit/spring/config/config.properties",
    "classpath:io/mykit/spring/config/config_${anotherfile.configinject}.properties"})
public class ConfigurationFileInject{
    // 這里的值來自application.properties,spring boot啟動時默認加載此文件
    @Value("${app.name}")
    private String appName; 

    // 注入第一個配置外部文件屬性
    @Value("${book.name}")
    private String bookName; 

    // 注入第二個配置外部文件屬性
    @Value("${book.name.placeholder}")
    private String bookNamePlaceholder; 

    // 注入環境變量對象,存儲注入的屬性值
    @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.toString();
    }   
}

3.@Value中#{..}和${...}的區別

我們這里提供一個測試屬性文件:advance_value_inject.properties,大致的內容如下所示。

server.name=server1,server2,server3
author.name=binghe

測試類AdvanceValueInject:引入advance_value_inject.properties文件,作為屬性的注入

@Component
@PropertySource({"classpath:io/mykit/spring/config/advance_value_inject.properties"})
public class AdvanceValueInject {
...
}

${...}的用法

{}里面的內容必須符合SpEL表達式, 通過@Value(“${spelDefault.value}”)可以獲取屬性文件中對應的值,但是如果屬性文件中沒有這個屬性,則會報錯。可以通過賦予默認值解決這個問題,如下所示。

@Value("${author.name:binghe}")

上述代碼的含義表示向bean的屬性中注入配置文件中的author.name屬性的值,如果配置文件中沒有author.name屬性,則向bean的屬性中注入默認值binghe。例如下面的代碼片段。

@Value("${author.name:binghe}")
private String name;

#{…}的用法

// 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執行外層的${}時,內部的#{}為空,所以會執行失敗!

@Value注解用法小結:

  • #{…} 用於執行SpEl表達式,並將內容賦值給屬性。
  • ${…} 主要用於加載外部屬性文件中的值。
  • #{…} 和${…} 可以混合使用,但是必須#{}外面,${}在里面。

@Value注解案例

這里,我們還是以一個小案例的形式來說明。

首先,我們來創建一個Person類作為測試的bean組件,如下所示。

package io.mykit.spring.plugins.register.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試實體類
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {
    private static final long serialVersionUID = 7387479910468805194L;
    private String name;
    private Integer age;
}

接下來,創建一個新的配置類PropertyValueConfig,用來配置Spring的bean組件,我們在PropertyValueConfig類中將Person類的對象注冊到IOC容器中,如下所示。

package io.mykit.spring.plugins.register.config;
import io.mykit.spring.plugins.register.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試屬性賦值
 */
@Configuration
public class PropertyValueConfig {
    @Bean
    public Person person(){
        return new Person();
    }
}

我們再來創建一個測試類PropertyValueTest,在PropertyValueTest類中創建測試方法testPropertyValue01(),並在testPropertyValue01()方法中通過PropertyValueConfig類創建AnnotationConfigApplicationContext對象,打印出目前IOC容器中存在的bean名稱,如下所示。

package io.mykit.spring.test;
import io.mykit.spring.plugins.register.config.PropertyValueConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試bean的生命周期
 */
public class PropertyValueTest {
    @Test
    public void testPropertyValue01(){
        //創建IOC容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertyValueConfig.class);
        String[] names = context.getBeanDefinitionNames();
        Arrays.stream(names).forEach(System.out::println);
    }
}

此時,我們運行PropertyValueTest類的testPropertyValue01()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
propertyValueConfig
person

從輸出的結果信息中,可以看出,IOC容器中除了Spring框架注冊的bean之外,還包含我們自己向IOC容器中注冊的bean組件:propertyValueConfig和person。

接下來,我們改造下PropertyValueTest類的testPropertyValue01()方法,輸出Person對象的信息,如下所示。

package io.mykit.spring.test;
import io.mykit.spring.plugins.register.bean.Person;
import io.mykit.spring.plugins.register.config.PropertyValueConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Arrays;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試bean的生命周期
 */
public class PropertyValueTest {
    @Test
    public void testPropertyValue01(){
        //創建IOC容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertyValueConfig.class);
        String[] names = context.getBeanDefinitionNames();
        Arrays.stream(names).forEach(System.out::println);

        System.out.println("================================");
        Person person = (Person) context.getBean("person");
        System.out.println(person);
    }
}

接下來,再次運行PropertyValueTest類的testPropertyValue01()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
propertyValueConfig
person
================================
Person(name=null, age=null)

可以看到,向IOC容器中注冊的Person對象的name屬性為null,age屬性為null。那如何向Person對象的name屬性和age屬性賦值呢?此時,Spring中的@Value注解就派上了用場。

如果我們通過XML文件為bean的屬性賦值,則可以通過如下配置的方式實現。

<bean id = "person" class="io.mykit.spring.plugins.register.bean.Person">
    <property name="name" value="binghe"></property>
    <property name="age" value="18"></property>
</bean>

如果使用注解該如何實現呢?別急,往下看!

我們可以在Person類的屬性上使用@Value注解為屬性賦值,如下所示。

package io.mykit.spring.plugins.register.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import java.io.Serializable;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試實體類
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {
    private static final long serialVersionUID = 7387479910468805194L;
    @Value("binghe")
    private String name;
    @Value("#{20-2}")
    private Integer age;
}

此時,我們再次運行PropertyValueTest類的testPropertyValue01()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
propertyValueConfig
person
================================
Person(name=binghe, age=18)

可以看到,使用@Value注解已經向Person對象的name屬性中注入了binghe,向age屬性中注入了18。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最后

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring注解驅動開發。公眾號回復“spring注解”關鍵字,領取Spring注解驅動開發核心知識圖,讓Spring注解驅動開發不再迷茫。

部分參考:blog.csdn.net/hry2015/article/details/72453920


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM