摆脱困境:将属性值注入配置Bean
Spring Framework對(duì)將從屬性文件中找到的屬性值注入到bean或@Configuration類中提供了很好的支持。 但是,如果將單個(gè)屬性值注入這些類中,則會(huì)遇到一些問(wèn)題。
這篇博客文章指出了這些問(wèn)題,并描述了我們?nèi)绾谓鉀Q它們。
讓我們開(kāi)始吧。
如果使用Spring Boot,則應(yīng)使用其Typesafe配置屬性。 您可以從以下網(wǎng)頁(yè)中獲取有關(guān)此信息的更多信息:
- Spring Boot參考手冊(cè)的23.7節(jié)Typesafe配置屬性
- @EnableConfigurationProperties批注的Javadoc
- @ConfigurationProperties批注的Javadoc
- 在Spring Boot中使用@ConfigurationProperties
很簡(jiǎn)單,但并非沒(méi)有問(wèn)題
如果將單個(gè)屬性值注入到我們的bean類中,我們將面臨以下問(wèn)題:
1.注入多個(gè)屬性值很麻煩
如果我們通過(guò)使用@Value批注注入單個(gè)屬性值,或者通過(guò)使用Environment對(duì)象來(lái)獲取屬性值,則注入多個(gè)屬性值會(huì)很麻煩。
假設(shè)我們必須向UrlBuilder對(duì)象注入一些屬性值。 該對(duì)象需要三個(gè)屬性值:
- 服務(wù)器的主機(jī)( app.server.host )
- 服務(wù)器監(jiān)聽(tīng)的端口( app.server.port )
- 使用的協(xié)議( app.server.protocol )
當(dāng)UrlBuilder對(duì)象構(gòu)建用于訪問(wèn)Web應(yīng)用程序的不同功能的URL地址時(shí),將使用這些屬性值。
如果我們通過(guò)使用構(gòu)造函數(shù)注入和@Value注釋注入這些屬性值,則UrlBuilder類的源代碼如下所示:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;@Component public class UrlBuilder {private final String host;private final String port;private final String protocol;@Autowiredpublic UrlBuilder(@Value("${app.server.protocol}") String protocol,@Value("${app.server.host}") String serverHost,@Value("${app.server.port}") int serverPort) {this.protocol = protocol.toLowercase();this.serverHost = serverHost;this.serverPort = serverPort;} }補(bǔ)充閱讀:
- @Value注釋的Javadoc
如果我們通過(guò)使用構(gòu)造函數(shù)注入和Environment類注入這些屬性值,則UrlBuilder類的源代碼如下所示:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component;@Component public class UrlBuilder {private final String host;private final String port;private final String protocol;@Autowiredpublic UrlBuilder(Environment env) {this.protocol = env.getRequiredProperty("app.server.protocol").toLowercase();this.serverHost = env.getRequiredProperty("app.server.host");this.serverPort = env.getRequiredProperty("app.server.port", Integer.class);} }補(bǔ)充閱讀:
- 環(huán)境接口的Javadoc
我承認(rèn)這看起來(lái)還不錯(cuò)。 但是,當(dāng)所需屬性值的數(shù)量增加和/或我們的類也具有其他依賴項(xiàng)時(shí),將所有這些屬性都注入是很麻煩的。
2.我們必須指定多個(gè)屬性名稱(或記住使用常量)
如果我們將單個(gè)屬性值直接注入需要它們的Bean中,并且有多個(gè)Bean(A和B)需要相同的屬性值,那么我們想到的第一件事就是在兩個(gè)Bean類中指定屬性名稱:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class A {private final String protocol;@Autowiredpublic A(@Value("${app.server.protocol}") String protocol) {this.protocol = protocol.toLowercase();} }import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class B {private final String protocol;@Autowiredpublic B(@Value("${app.server.protocol}") String protocol) {this.protocol = protocol.toLowercase();} }這是一個(gè)問(wèn)題,因?yàn)?
我們可以通過(guò)將屬性名稱移到常量類來(lái)解決此問(wèn)題。 如果這樣做,我們的源代碼如下所示:
public final PropertyNames {private PropertyNames() {}public static final String PROTOCOL = "${app.server.protocol}"; }import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class A {private final String protocol;@Autowiredpublic A(@Value(PropertyNames.PROTOCOL) String protocol) {this.protocol = protocol.toLowercase();} }import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class B {private final String protocol;@Autowiredpublic B(@Value(PropertyNames.PROTOCOL) String protocol) {this.protocol = protocol.toLowercase();} }這可以解決維護(hù)問(wèn)題,但前提是所有開(kāi)發(fā)人員都記得要使用它。 我們當(dāng)然可以通過(guò)使用代碼審閱來(lái)強(qiáng)制執(zhí)行此操作,但這是審閱者必須記住要檢查的另一件事。
3.添加驗(yàn)證邏輯成為問(wèn)題
假設(shè)我們有兩個(gè)類( A和B ),它們需要app.server.protocol屬性的值。 如果我們將此屬性值直接注入到A和B Bean中,并且想要確保該屬性的值為'http'或'https',則必須
如果我們將驗(yàn)證邏輯添加到兩個(gè)bean類,則這些類的源代碼如下所示:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class A {private final String protocol;@Autowiredpublic A(@Value("${app.server.protocol}") String protocol) {checkThatProtocolIsValid(protocol);this.protocol = protocol.toLowercase();}private void checkThatProtocolIsValid(String protocol) {if (!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("https")) {throw new IllegalArgumentException(String.format("Protocol: %s is not allowed. Allowed protocols are: http and https.",protocol));}} }import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class B {private final String protocol;@Autowiredpublic B(@Value("${app.server.protocol}") String protocol) {checkThatProtocolIsValid(protocol);this.protocol = protocol.toLowercase();}private void checkThatProtocolIsValid(String protocol) {if (!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("https")) {throw new IllegalArgumentException(String.format("Protocol: %s is not allowed. Allowed protocols are: http and https.",protocol));}} }這是一個(gè)維護(hù)問(wèn)題,因?yàn)锳和B類包含復(fù)制粘貼代碼。 通過(guò)將驗(yàn)證邏輯移至實(shí)用程序類并在創(chuàng)建新的A和B對(duì)象時(shí)使用它,可以稍微改善這種情況。
完成此操作后,我們的源代碼如下所示:
public final class ProtocolValidator {private ProtocolValidator() {}public static void checkThatProtocolIsValid(String protocol) {if (!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("https")) {throw new IllegalArgumentException(String.format("Protocol: %s is not allowed. Allowed protocols are: http and https.",protocol));}} }import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class A {private final String protocol;@Autowiredpublic A(@Value("${app.server.protocol}") String protocol) {ProtocolValidator.checkThatProtocolIsValid(protocol);this.protocol = protocol.toLowercase();} }import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class B {private final String protocol;@Autowiredpublic B(@Value("${app.server.protocol}") String protocol) {ProtocolValidator.checkThatProtocolIsValid(protocol);this.protocol = protocol.toLowercase();} }問(wèn)題在于,我們?nèi)匀槐仨氂涀∫{(diào)用此實(shí)用程序方法。 我們當(dāng)然可以通過(guò)使用代碼審閱來(lái)強(qiáng)制執(zhí)行此操作,但是再次重申,審閱者必須記住要檢查的另一件事。
4.我們不能編寫好的文檔
我們不能編寫描述應(yīng)用程序配置的好的文檔,因?yàn)槲覀儽仨殞⒋宋臋n添加到實(shí)際的屬性文件中,使用Wiki或編寫* gasp * Word文檔。
這些選項(xiàng)中的每個(gè)選項(xiàng)都會(huì)引起問(wèn)題,因?yàn)槲覀儾荒茉诰帉懶枰獜膶傩晕募姓业綄傩灾档拇a的同時(shí)使用它們。 如果需要閱讀文檔,則必須打開(kāi)“外部文檔”,這會(huì)導(dǎo)致上下文切換非常昂貴 。
讓我們繼續(xù)前進(jìn),找出如何解決這些問(wèn)題。
將屬性值注入配置Bean
通過(guò)將屬性值注入配置Bean中,我們可以解決前面提到的問(wèn)題。 首先,為示例應(yīng)用程序創(chuàng)建一個(gè)簡(jiǎn)單的屬性文件。
創(chuàng)建屬性文件
我們要做的第一件事是創(chuàng)建一個(gè)屬性文件。 我們的示例應(yīng)用程序的屬性文件稱為application.properties ,其外觀如下:
app.name=Configuration Properties example app.production.mode.enabled=falseapp.server.port=8080 app.server.protocol=http app.server.host=localhost讓我們繼續(xù)并配置示例應(yīng)用程序的應(yīng)用程序上下文。
配置應(yīng)用程序上下文
我們的示例應(yīng)用程序的應(yīng)用程序上下文配置類有兩個(gè)目標(biāo):
通過(guò)執(zhí)行以下步驟,我們可以實(shí)現(xiàn)其第二個(gè)第二個(gè)目標(biāo):
WebAppContext類的源代碼如下所示:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc;@Configuration @ComponentScan({"net.petrikainulainen.spring.trenches.config","net.petrikainulainen.spring.trenches.web" }) @EnableWebMvc @PropertySource("classpath:application.properties") public class WebAppContext {/*** Ensures that placeholders are replaced with property values*/@BeanPropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {return new PropertySourcesPlaceholderConfigurer();} }補(bǔ)充閱讀:
- @ComponentScan批注的Javadoc
- @PropertySource批注的Javadoc
- Spring框架參考手冊(cè)的5.13.4 @PropertySource部分
- PropertySourcesPlaceholderConfigurer類的Javadoc
下一步是創(chuàng)建配置Bean類,并將從屬性文件中找到的屬性值注入到它們中。 讓我們找出如何做到這一點(diǎn)。
創(chuàng)建配置Bean類
讓我們創(chuàng)建以下描述的兩個(gè)配置bean類:
- WebProperties類包含用于配置使用的協(xié)議,服務(wù)器的主機(jī)以及服務(wù)器偵聽(tīng)的端口的屬性值。
- ApplicationProperties類包含用于配置應(yīng)用程序名稱并標(biāo)識(shí)是否啟用生產(chǎn)模式的屬性值。 它還具有對(duì)WebProperties對(duì)象的引用。
首先 ,我們必須創(chuàng)建WebProperties類。 我們可以按照以下步驟進(jìn)行操作:
WebProperties類的源代碼如下所示:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;@Component public final class WebProperties {private final String protocol;private final String serverHost;private final int serverPort;@Autowiredpublic WebProperties(@Value("${app.server.protocol}") String protocol,@Value("${app.server.host}") String serverHost,@Value("${app.server.port}") int serverPort) {checkThatProtocolIsValid(protocol);this.protocol = protocol.toLowercase();this.serverHost = serverHost;this.serverPort = serverPort;}private void checkThatProtocolIsValid(String protocol) {if (!protocol.equalsIgnoreCase("http") && !protocol.equalsIgnoreCase("https")) {throw new IllegalArgumentException(String.format("Protocol: %s is not allowed. Allowed protocols are: http and https.",protocol));}}public String getProtocol() {return protocol;}public String getServerHost() {return serverHost;}public int getServerPort() {return serverPort;} }其次 ,我們必須實(shí)現(xiàn)ApplicationProperties類。 我們可以按照以下步驟進(jìn)行操作:
ApplicationProperties類的源代碼如下所示:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;@Component public final class ApplicationProperties {private final String name;private final boolean productionModeEnabled;private final WebProperties webProperties;@Autowiredpublic ApplicationProperties(@Value("${app.name}") String name,@Value("${app.production.mode.enabled:false}") boolean productionModeEnabled,WebProperties webProperties) {this.name = name;this.productionModeEnabled = productionModeEnabled;this.webProperties = webProperties;}public String getName() {return name;}public boolean isProductionModeEnabled() {return productionModeEnabled;}public WebProperties getWebProperties() {return webProperties;} }讓我們繼續(xù)研究該解決方案的好處。
這對(duì)我們有何幫助?
現(xiàn)在,我們創(chuàng)建了包含從application.properties文件中找到的屬性值的Bean類。 該解決方案可能看起來(lái)像是過(guò)度設(shè)計(jì),但與傳統(tǒng)的簡(jiǎn)單方法相比,它具有以下優(yōu)點(diǎn):
1.我們只能注入一個(gè)Bean而不是多個(gè)屬性值
如果我們將屬性值注入到配置Bean中,然后通過(guò)使用構(gòu)造函數(shù)注入將該配置Bean注入U(xiǎn)rlBuilder類,則其源代碼如下所示:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class UrlBuilder {private final WebProperties properties;@Autowiredpublic UrlBuilder(WebProperties properties) {this.properties = properties;} }如我們所見(jiàn),這使我們的代碼更整潔( 尤其是在使用構(gòu)造函數(shù)注入的情況下 )。
2.我們只需指定一次屬性名稱
如果將屬性值注入到配置Bean中,則只能在一個(gè)地方指定屬性名稱。 這意味著
- 我們的代碼遵循關(guān)注點(diǎn)分離原則。 可從配置Bean中找到屬性名稱,而其他需要此信息的Bean不知道其來(lái)源。 他們只是使用它。
- 我們的代碼遵循“ 不要重復(fù)自己”的原則。 因?yàn)閷傩悦Q僅在一個(gè)位置(在配置bean中)指定,所以我們的代碼更易于維護(hù)。
另外,(IMO)我們的代碼看起來(lái)也更加簡(jiǎn)潔:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class A {private final String protocol;@Autowiredpublic A(WebProperties properties) {this.protocol = properties.getProtocol();} }import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class B {private final String protocol;@Autowiredpublic B(WebProperties properties) {this.protocol = properties.getProtocol();} }3.我們只需要編寫一次驗(yàn)證邏輯
如果將屬性值注入到配置Bean中,則可以將驗(yàn)證邏輯添加到配置Bean中,而其他Bean不必知道它。 這種方法具有三個(gè)好處:
- 我們的代碼遵循關(guān)注點(diǎn)分離原則,因?yàn)榭梢詮呐渲胋ean(它所屬的地方)中找到驗(yàn)證邏輯。 其他的豆子不必知道。
- 我們的代碼遵循“不要重復(fù)自己”的原則,因?yàn)轵?yàn)證邏輯是從一個(gè)地方找到的。
- 創(chuàng)建新的Bean對(duì)象時(shí),我們不必記住調(diào)用驗(yàn)證邏輯,因?yàn)樵趧?chuàng)建配置Bean時(shí)我們可以強(qiáng)制執(zhí)行驗(yàn)證規(guī)則。
另外,我們的源代碼看起來(lái)也更加干凈:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class A {private final String protocol;@Autowiredpublic A(WebProperties properties) {this.protocol = properties.getProtocol();} }import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class B {private final String protocol;@Autowiredpublic B(WebProperties properties) {this.protocol = properties.getProtocol();} }4.我們可以從IDE中訪問(wèn)文檔
我們可以通過(guò)向配置bean中添加Javadoc注釋來(lái)記錄應(yīng)用程序的配置。 完成此操作后,當(dāng)我們編寫需要這些屬性值的代碼時(shí),可以從IDE中訪問(wèn)此文檔。 我們不需要打開(kāi)其他文件或閱讀維基頁(yè)面。 我們可以簡(jiǎn)單地繼續(xù)編寫代碼,避免上下文切換的開(kāi)銷 。
讓我們繼續(xù)并總結(jié)從這篇博客文章中學(xué)到的知識(shí)。
摘要
這篇博客文章告訴我們將屬性值注入配置Bean:
- 幫助我們遵循關(guān)注點(diǎn)分離原則。 有關(guān)配置屬性和屬性值驗(yàn)證的內(nèi)容封裝在我們的配置bean中。 這意味著使用這些配置bean的bean不知道屬性值來(lái)自何處或如何驗(yàn)證它們。
- 幫助我們遵循“不要重復(fù)自己”的原則,因?yàn)?)我們只需指定一次屬性名稱,然后2)可以將驗(yàn)證邏輯添加到配置bean。
- 使我們的文檔更易于訪問(wèn)。
- 使我們的代碼更易于編寫,閱讀和維護(hù)。
但是,這無(wú)助于我們弄清應(yīng)用程序的運(yùn)行時(shí)配置。 如果我們需要此信息,則必須讀取從服務(wù)器中找到的屬性文件。 這很麻煩。
我們將在我的下一篇博客文章中解決此問(wèn)題。
- PS:您可以從Github獲得此博客文章的示例應(yīng)用程序 。
翻譯自: https://www.javacodegeeks.com/2015/04/spring-from-the-trenches-injecting-property-values-into-configuration-beans.html
總結(jié)
以上是生活随笔為你收集整理的摆脱困境:将属性值注入配置Bean的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux内存进程按使用大小排序(lin
- 下一篇: linux菜单栏删除怎么恢复(linux