注入屬性檔案的值
按照以往的方式,我們總是直接把具體的字面量值填入程式碼進行字面量值的注入。如下所示:
1 @Component 2 public class Music { 3 private String musicName = null; 4 private Date publishTime = null; 5 6 @Value("Dream") 7 public void setMusicName(String musicName) { 8 this.musicName = musicName; 9 } 10 11 @Value("2022/12/22")12 public void setPublishTime(Date publishTime) { 13 this.publishTime = publishTime; 14 } 15 16 // 省略getter方法 17 }
可以看到Music定義了三個屬性。其值直接填在程式碼裡,由@Value註解注入。直覺告訴我們,這種硬編碼的注入方式並不友好。假如Bean的屬性值由於某種原因需做變動,我們還得修改程式碼。非常麻煩,非常不好維護!要是能從某個屬性檔案裡讀取屬性值之後作為字面量值進行注入,該有多好!這樣,我們無需修改程式碼,只需修改屬性檔案就能達到修改Bean的屬性值的目的。非常靈活,非常簡單,非常方便,非常易於修改,非常易於維護!幸運的是,Spring確實提供了這樣的支援。而這支援,得從Environment介面談起。
Environment是Spring定義的一個介面,代表著當前正在執行的應用程式的環境,主要提供兩種功能:一種是設定Profile;一種是能讓開發人員比較方便地訪問屬性檔案。至於何為Profile,我們將在其它章節另行介紹。現在重點關注的是與屬性檔案訪問相關的內容。
具體而言,Spring實現了兩種Environment介面:一種是StandardEnvironment,用於與Web無關的應用程式;一種是StandardServletEnvironment,用於與Web相關的應用程式。建立Spring容器時,Spring容器將會建立Environment,而Environment則會載入屬性檔案。因此,我們需要提供配置資訊告訴Environment屬性檔案在哪。而這,可以通過Spring提供的@PropertySource註解指定。如下所示:
1 @Configuration 2 @ComponentScan("com.dream.controller") 3 @PropertySource("classpath:com/dream/app.properties") 4 public class ServletConfig { 5 }
我們指定的屬性檔案是classpath:com/dream/app.properties,位於com.dream包裡。屬性檔案的內容如下:
music.name=Dream music.publishtime=2022/12/22
於是,Environment能從@PropertySource註解指定的位置載入屬性檔案。之後,我們需把Environment注入我們的Bean裡,以使我們的Bean能用Environment獲取屬性檔案的屬性值,按照屬性名的匹配情況把相應的屬性值注入我們的Bean裡,如下所示:
1 @Component 2 public class Music { 3 private Environment environment = null; 4 private String musicName = null; 5 private Date publishTime = null; 6 7 @Autowired 8 public Music(Environment environment) { 9 this.environment = environment; 10 this.musicName = environment.getProperty("music.name"); 11 this.publishTime = environment.getProperty("music.publishtime", Date.class); 12 } 13 14 // 省略getter方法 15 }
通過@Autowired註解,我們告訴Spring容器把Environment注入Music的建構函式裡。建構函式拿到Environment之後,以屬性檔案的屬性名作為引數呼叫getProperty()方法獲取屬性值,把屬性值賦給Bean的屬性,完成字面量值的注入。getProperty()方法具有四種過載,簽名如下:
1.String getProperty(String key)
2.String getProperty(String key, String defaultValue)
3.T getProperty(String key, Class<T> type)
4.T getProperty(String key, Class<T> type, T defaultValue)
大家一看便知怎麼呼叫,無需詳敘。重點在於,我們還可通過往@Value註解裡填入屬性佔位符的方式注入屬性檔案的值。而這,需要我們建立PropertySourcesPlaceholderConfigurer型別的Bean,讓它幫助我們針對這種方式進行處理。如下所示:
1 @Configuration 2 @ComponentScan("com.dream.controller") 3 public class ServletConfig { 4 @Bean 5 public PropertySourcesPlaceholderConfigurer placeholderConfigurer( 6 ResourceLoader resourceLoader) { 7 var resourceUrl = "classpath:com/dream/app.properties"; 8 var resource = resourceLoader.getResource(resourceUrl); 9 var configurer = new PropertySourcesPlaceholderConfigurer(); 10 configurer.setLocations(resource); 11 return configurer; 12 } 13 }
可以看到先前新增的@PropertySource註解已被刪除,卻定義了一個具有ResourceLoader型別的引數的方法,用於建立PropertySourcesPlaceholderConfigurer型別的Bean。建立過程如下:
1.呼叫resourceLoader的getResource()方法獲取屬性檔案這種資源。
2.建立PropertySourcesPlaceholderConfigurer型別的例項。
3.呼叫setLocations()方法,把屬性檔案這種資源交給PropertySourcesPlaceholderConfigurer載入。
注意:setLocations()方法的引數是Resource...型別的,可以指定多個代表屬性檔案的資源。
於是,Spring容器載入配置檔案之後,PropertySourcesPlaceholderConfigurer型別的Bean就存在Spring容器裡,幫助我們處理屬性佔位符。因此,我們還得修改Music如下:
1 @Component 2 public class Music { 3 private String musicName = null; 4 private Date publishTime = null; 5 6 @Value("${music.name}") 7 public void setMusicName(String musicName) { 8 this.musicName = musicName; 9 } 10 11 @Value("${music.publishtime}") 12 public void setPublishTime(Date publishTime) { 13 this.publishTime = publishTime; 14 } 15 16 // 省略getter方法 17 }
可以看到@Value註解注入的是屬性佔位符。屬性佔位符是這樣構成的:${屬性檔案裡的屬性名},也就是用${}把屬性檔案裡的屬性名括起來。這樣,實現了BeanFactoryPostProcessor介面的PropertySourcesPlaceholderConfigurer就能在Spring容器建立Bean之前做些事情:
1. 把由setLocations()方法指定的屬性檔案載入到Environment裡。
2. 找出Bean的定義裡的屬性佔位符,與Environment裡的屬性名進行匹配,並把匹配成功的屬性佔位符改成屬性名對應的屬性值。
於是,那些含有屬性佔位符的Bean的定義都被修改了,由屬性佔位符改成屬性檔案的值。之後,Spring容器根據修改之後的Bean的定義建立和裝配Bean,注入的值自然是屬性檔案的值。
另外,如果配置檔案是XML的話,可以這樣配置:
1 <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> 2 <property name="locations" value="classpath:com/dream/app.properties"/> 3 </bean> 4 <bean class="com.dream.controller.Music"> 5 <property name="musicName" value="${music.name}" /> 6 <property name="publishTime" value="${music.publishtime}" /> 7 </bean>
為了方便配置,Spring還專門提供了<context:property-placeholder>元素,用於建立PropertySourcesPlaceholderConfigurer型別的Bean。因此,配置資訊也能改成這樣:
1 <context:property-placeholder locations ="classpath:com/dream/app.properties"/> 2 3 <bean class="com.dream.controller.Music"> 4 <property name="musicName" value="${music.name}" /> 5 <property name="publishTime" value="${music.publishtime}" /> 6 </bean>
還有,Environment除了能夠載入屬性檔案,也能按照優先順序從上到下載入這些引數:
1.Servlet初始化引數
2.Servlet上下文初始化引數
3.JNDI環境變數
4.JVM系統屬性(也就是JVM的命令列引數)
5.作業系統環境變數
這意味著我們也能通過屬性佔位符注入這些引數的值。大家可以試試,這裡不作過多介紹。
於是,關於怎樣通過屬性佔位符注入屬性檔案的值,我們已經理清楚了。下章該談談Spring表示式語言,看看能用強大的Spring表示式語言注入怎樣的字面量值。歡迎大家繼續閱讀,謝謝大家!