Spring提供了2種方式在運行時注入值:
- 屬性占位符(Property placeholder)
- 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:我是一名奮斗在上海的程序員)。