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

冰河團隊發表於2020-06-30

寫在前面

在之前的文章中,我們探討了如何向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

相關文章