Spring入門(九):運行時值注入


Spring提供了2種方式在運行時注入值:

  1. 屬性占位符(Property placeholder)
  2. Spring表達式語言(SpEL)

1. 屬性占位符

1.1 注入外部的值

1.1.1 使用Environment

一般情況下,我們會將一些值放到配置文件中,等程序運行時再把值注入到一些字段上。

假如,我們有一個test.properties配置文件,內容如下:

book.author=wangyunfei
book.name=spring boot
author.age=30

現在我們希望在程序運行時,把這個值分別賦值給字段bookAuthor和bookName,那么該如何實現呢?

首先,新建配置類ExpressiveConfig如下:

package chapter03.el;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@ComponentScan
@PropertySource("classpath:chapter03/el/test.properties")
public class ExpressiveConfig {
    @Autowired
    private Environment environment;

    public void outputResource() {
        System.out.println("book.name:" + environment.getProperty("book.name"));
        System.out.println("book.author:" + environment.getProperty("book.author"));
    }
}

這里我們使用@PropertySource注解引用了test.properties配置文件,這個文件的位置位於chapter03.el包下。

這個屬性文件會加載到Spring的Environment中,然后我們就可以調用getProperty()方法獲取到屬性值。

新建Main類,在其main()方法中添加如下測試代碼:

package chapter03.el;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ExpressiveConfig.class);

        ExpressiveConfig expressiveConfig = context.getBean(ExpressiveConfig.class);

        expressiveConfig.outputResource();

        context.close();
    }
}

運行代碼,發現拋出java.io.FileNotFoundException異常,如下所示:

從報錯信息可以看出, 這是提示找不到chapter03/el/test.properties這個文件,這是為什么呢?

帶着這個疑問,我們看下target目錄下編譯后的代碼,如下所示:

從圖中可以看出,我們新建的test.properties和test.txt文件並沒有被編譯到target目錄下,所以才會拋出異常。

這是因為,我們新建文件的位置放在chapter03.el包下,而IDEA默認是不會把這些文件自動復制到target目錄下的,但我們可以在pom.xml中添加如下配置來解決該問題:

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.txt</include>
                <include>**/*.properties</include>
            </includes>
        </resource>
    </resources>
</build>

這里我們指定了txt和properties文件,如果需要,可以繼續添加<include>標簽指定xml等文件。

再次運行測試代碼,輸出日志如下所示:

book.name:spring boot

book.author:wangyunfei

此時target目錄下已經包含了我們新建的2個文件:

如果指定的屬性值不存在,getProperty()會返回null,如下所示:

String workCity = environment.getProperty("author.workcity");
System.out.println("author.workcity:" + workCity);

輸出結果:

author.workcity:null

getProperty()還提供了1個重載,當指定的屬性值不存在時,可以指定默認值:

String workCity = environment.getProperty("author.workcity", "上海");
System.out.println("author.workcity:" + workCity);

輸出結果:

author.workcity:上海

如果希望屬性值必須存在,可以使用getRequiredProperty()方法,當屬性值不存在時,會拋出java.lang.IllegalStateException異常:

String workCity = environment.getRequiredProperty("author.workcity");
System.out.println("author.workcity:" + workCity);

getProperty()還提供了1個重載,可以指定返回值的類型,比如我們想返回Integer類型:

Integer authorAge = environment.getProperty("author.age", Integer.class);
System.out.println("author.age:" + authorAge);

輸出結果:

author.age:30

getProperty()還提供了1個重載,當指定的屬性值不存在時,不僅可以指定默認值,還可以指定返回值類型:

boolean isMan = environment.getProperty("author.isMan", Boolean.class, true);
System.out.println("author.isMan:" + isMan);

輸出結果:

author.isMan:true

1.1.2 使用屬性占位符

除了使用Environment獲取外部的屬性值,我們還可以使用屬性占位符來獲取。

在Spring裝配中,占位符的形式為使用“${......}”包裝的屬性名稱。

新建Book類如下:

package chapter03.el;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Book {
    @Value("${book.name}")
    private String bookName;

    @Value("${book.author}")
    private String bookAuthor;

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getBookAuthor() {
        return bookAuthor;
    }

    public void setBookAuthor(String bookAuthor) {
        this.bookAuthor = bookAuthor;
    }
}

可以發現,我們在字段上添加了@Value注解,參數傳的值就是屬性占位符,用來獲取屬性文件中指定的屬性值。

然后,在ExpressiveConfig配置類中添加如下代碼:

@Autowired
private Book book;

public void outputResource() {
     System.out.println("book.name:" + book.getBookName());
     System.out.println("book.author:" + book.getBookAuthor());
}

輸出結果:

book.name:spring boot

book.author:wangyunfei

2. Spring表達式語言

Spring表達式語言(Spring Expression Language,SpEL)是一種非常靈活的表達式語言,能夠以一種強大和簡潔的方式將值裝配到bean屬性或者構造器參數中,在這個過程中所使用的的表達式會在運行時計算值。

SpEL表達式要放到“#{......}”之中,而之前講到的屬性占位符是放到“${......}”之中。

接下來,我們分場景來看下Spring表達式語言的使用方法。

2.1 引用系統屬性值

在ExpressiveConfig中添加如下代碼:

@Value("#{systemProperties['os.name']}")
private String osName;

public void outputResource() {
    System.out.println("os.name:" + osName);
}

輸出結果:

os.name:Windows 7

2.2 引用bean的屬性和方法

首先,新建一個類DemoService如下所示:

package chapter03.el;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class DemoService {
    @Value("DemoService類的another屬性")
    private String another;

    public String getAnother() {
        return another;
    }

    public void setAnother(String another) {
        this.another = another;
    }
}

然后,在ExpressiveConfig中添加如下代碼:

@Value("#{demoService.another}")
private String fromAnother;

public void outputResource() {
    System.out.println("demoService.another:" + fromAnother);
}

表達式中的demoService為DemoService bean的ID,another是它的屬性。

輸出結果:

demoService.another:DemoService類的another屬性

表達式也可以修改為調用bean的方法:

@Value("#{demoService.getAnother()}")
private String fromAnother;

輸出結果不變,只是從調用屬性變成了調用方法。

調用完方法,可以對方法的返回值繼續調用其它方法,比如toUpperCase():

@Value("#{demoService.getAnother()?.toUpperCase()}")
private String fromAnother;

之所以使用"?."運算符,是為了避免當demoService.getAnother()返回null時,代碼出現NullPointerException。

此時的輸出結果為:

demoService.another:DEMOSERVICE類的ANOTHER屬性

2.3 在表達式中使用類型

使用表達式生成1個隨機數:

@Value("#{T(java.lang.Math).random()}")
private double randomNumber;

public void outputResource() {
    System.out.println("randomNumber:" + randomNumber);
}

這里我們使用T()引用了java.lang.Math類,然后調用了它的靜態方法random()。

輸出結果:

randomNumber:0.6801944394506442

2.4 使用運算符

上面的例子中,生成隨機數后,我們還可以使用乘法運算符,如下所示:

@Value("#{T(java.lang.Math).random() * 100.0}")
private double randomNumber;

我們也可以在表達式中使用“+”運算符拼接字符串,如下所示:

@Value("#{book.getBookName() + ' write by ' + book.getBookAuthor()}")
private String bookDescr;

public void outputResource() {
    System.out.println("bookDescr:" + bookDescr);
}

其中book為Book bean的ID,輸出結果如下所示:

bookDescr:spring boot write by wangyunfei

也可以在表達式中使用三元運算符:

@Value("#{systemProperties['os.name'] == 'Windows 7'?'Windows':'Linux'}")
private String osType;

public void outputResource() {
    System.out.println("osType:" + osType);
}

因為我的電腦系統是Windows 7,所以輸出結果如下所示:

osType:Windows

SpEL還支持很多的運算符,這里只是列舉了幾個常用的例子,有興趣的同學可以自己深入研究下。

3. 源碼及參考

源碼地址:https://github.com/zwwhnly/spring-action.git,歡迎下載。

Craig Walls 《Spring實戰(第4版)》

汪雲飛《Java EE開發的顛覆者:Spring Boot實戰》

IDEA maven項目src源代碼下的資源文件不自動復制到classes文件夾的解決方法

原創不易,如果覺得文章能學到東西的話,歡迎點個贊、評個論、關個注,這是我堅持寫作的最大動力。

如果有興趣,歡迎添加我的微信:zwwhnly,等你來聊技術、職場、工作等話題(PS:我是一名奮斗在上海的程序員)。


免責聲明!

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



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