bean注入属性_摆脱困境:将属性值注入配置Bean
bean注入屬性
Spring Framework對將從屬性文件中找到的屬性值注入到bean或@Configuration類中提供了很好的支持。 但是,如果將單個屬性值注入這些類中,則會遇到一些問題。
這篇博客文章指出了這些問題,并描述了我們如何解決它們。
讓我們開始吧。
如果使用Spring Boot,則應使用其Typesafe配置屬性。 您可以從以下網頁獲取有關此的更多信息:
- Spring Boot參考手冊的23.7節Typesafe配置屬性
- @EnableConfigurationProperties批注的Javadoc
- @ConfigurationProperties批注的Javadoc
- 在Spring Boot中使用@ConfigurationProperties
很簡單,但并非沒有問題
如果將單個屬性值注入到我們的bean類中,我們將面臨以下問題:
1.注入多個屬性值很麻煩
如果我們通過使用@Value批注注入單個屬性值,或者通過使用Environment對象來獲取屬性值,則注入多個屬性值會很麻煩。
假設我們必須向UrlBuilder對象注入一些屬性值。 該對象需要三個屬性值:
- 服務器的主機( app.server.host )
- 服務器監聽的端口( app.server.port )
- 使用的協議( app.server.protocol )
當UrlBuilder對象構建用于訪問Web應用程序不同功能的URL地址時,將使用這些屬性值。
如果我們使用構造函數注入和@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;} }補充閱讀:
- @Value注釋的Javadoc
如果我們通過使用構造函數注入和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);} }補充閱讀:
- 環境接口的Javadoc
我承認這看起來還不錯。 但是,當所需屬性值的數量增加和/或我們的類也具有其他依賴項時,將所有這些屬性注入都是很麻煩的。
2.我們必須指定多個屬性名稱(或記住使用常量)
如果我們將單個屬性值直接注入需要它們的bean中,并且多個bean(A和B)需要相同的屬性值,那么我們想到的第一件事就是在兩個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();} }這是一個問題,因為
我們可以通過將屬性名稱移到常量類來解決此問題。 如果這樣做,我們的源代碼如下所示:
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();} }這可以解決維護問題,但前提是所有開發人員都記得要使用它。 我們當然可以通過使用代碼審閱來強制執行此操作,但這是審閱者必須記住要檢查的另一件事。
3.添加驗證邏輯成為問題
假設我們有兩個類( A和B ),它們需要app.server.protocol屬性的值。 如果我們將此屬性值直接注入到A和B Bean中,并且想要確保該屬性的值為'http'或'https',則必須
如果我們將驗證邏輯添加到兩個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));}} }這是一個維護問題,因為A和B類包含復制粘貼代碼。 通過將驗證邏輯移至實用程序類并在創建新的A和B對象時使用它,可以稍微改善這種情況。
完成此操作后,我們的源代碼如下所示:
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();} }問題在于,我們仍然必須記住要調用此實用程序方法。 我們當然可以通過使用代碼審閱來強制執行此操作,但是再次重申,審閱者必須記住要檢查的另一件事。
4.我們不能編寫好的文檔
我們無法編寫描述應用程序配置的好的文檔,因為我們必須將此文檔添加到實際的屬性文件中,使用Wiki或編寫* gasp * Word文檔。
這些選項中的每個選項都會引起問題,因為我們不能在編寫需要從屬性文件中找到屬性值的代碼時同時使用它們。 如果需要閱讀文檔,則必須打開“外部文檔”,這會導致上下文切換非常昂貴 。
讓我們繼續前進,找出如何解決這些問題。
將屬性值注入配置Bean
通過將屬性值注入配置Bean中,我們可以解決前面提到的問題。 首先,為示例應用程序創建一個簡單的屬性文件。
創建屬性文件
我們要做的第一件事是創建一個屬性文件。 我們的示例應用程序的屬性文件稱為application.properties ,其外觀如下:
app.name=Configuration Properties example app.production.mode.enabled=falseapp.server.port=8080 app.server.protocol=http app.server.host=localhost讓我們繼續并配置示例應用程序的應用程序上下文。
配置應用程序上下文
我們的示例應用程序的應用程序上下文配置類有兩個目標:
通過執行以下步驟,我們可以實現第二個目標:
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();} }補充閱讀:
- @ComponentScan批注的Javadoc
- @PropertySource批注的Javadoc
- Spring框架參考手冊的5.13.4 @PropertySource部分
- PropertySourcesPlaceholderConfigurer類的Javadoc
下一步是創建配置Bean類,并將從屬性文件中找到的屬性值注入到它們中。 讓我們找出如何做到這一點。
創建配置Bean類
讓我們創建以下描述的兩個配置bean類:
- WebProperties類包含用于配置使用的協議,服務器的主機以及服務器偵聽的端口的屬性值。
- ApplicationProperties類包含用于配置應用程序名稱并標識是否啟用生產模式的屬性值。 它還具有對WebProperties對象的引用。
首先 ,我們必須創建WebProperties類。 我們可以按照以下步驟進行操作:
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;} }其次 ,我們必須實現ApplicationProperties類。 我們可以按照以下步驟進行操作:
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;} }讓我們繼續研究該解決方案的好處。
這對我們有什么幫助?
現在,我們創建了包含從application.properties文件中找到的屬性值的Bean類。 該解決方案似乎是一項過度的工程設計,但與傳統的簡單方法相比,它具有以下優點:
1.我們只能注入一個Bean而不是多個屬性值
如果我們將屬性值注入配置Bean,然后通過使用構造函數注入將該配置Bean注入UrlBuilder類,則其源代碼如下所示:
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;} }如我們所見,這使我們的代碼更整潔( 尤其是在使用構造函數注入的情況下 )。
2.我們只需指定一次屬性名稱
如果將屬性值注入到配置Bean中,則只能在一個地方指定屬性名稱。 這意味著
- 我們的代碼遵循關注點分離原則。 可從配置Bean中找到屬性名稱,而其他需要此信息的Bean不知道其來源。 他們只是使用它。
- 我們的代碼遵循“ 不要重復自己”的原則。 因為屬性名稱僅在一個位置(在配置Bean中)指定,所以我們的代碼更易于維護。
另外,(IMO)我們的代碼看起來也更加簡潔:
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.我們只需要編寫一次驗證邏輯
如果將屬性值注入到配置Bean中,則可以將驗證邏輯添加到配置Bean中,而其他Bean不必知道它。 這種方法具有三個好處:
- 我們的代碼遵循關注點分離原則,因為可以從配置bean(它所屬的地方)中找到驗證邏輯。 其他的豆子不必知道。
- 我們的代碼遵循“不要重復自己”的原則,因為驗證邏輯是從一個地方找到的。
- 創建新的bean對象時,我們不必記住要調用驗證邏輯,因為在創建配置bean時我們可以執行驗證規則。
同樣,我們的源代碼看起來也更加干凈:
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中訪問文檔
我們可以通過向配置bean中添加Javadoc注釋來記錄應用程序的配置。 完成此操作后,當我們編寫需要這些屬性值的代碼時,可以從IDE中訪問此文檔。 我們不需要打開其他文件或閱讀維基頁面。 我們可以簡單地繼續編寫代碼并避免上下文切換的開銷 。
讓我們繼續并總結從這篇博客文章中學到的知識。
摘要
這篇博客文章告訴我們將屬性值注入配置Bean:
- 幫助我們遵循關注點分離原則。 有關配置屬性和屬性值驗證的內容封裝在我們的配置Bean中。 這意味著使用這些配置bean的bean不知道屬性值來自何處或如何驗證它們。
- 幫助我們遵循“不要重復自己”的原則,因為1)我們只需指定一次屬性名稱,然后2)可以將驗證邏輯添加到配置bean。
- 使我們的文檔更易于訪問。
- 使我們的代碼更易于編寫,閱讀和維護。
但是,這無助于我們弄清應用程序的運行時配置。 如果我們需要此信息,則必須讀取從服務器中找到的屬性文件。 這很麻煩。
我們將在我的下一篇博客文章中解決此問題。
- PS:您可以從Github獲得此博客文章的示例應用程序 。
翻譯自: https://www.javacodegeeks.com/2015/04/spring-from-the-trenches-injecting-property-values-into-configuration-beans.html
bean注入屬性
總結
以上是生活随笔為你收集整理的bean注入属性_摆脱困境:将属性值注入配置Bean的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为 Mate 60 RS 机型曝光,消
- 下一篇: jvm崩溃的原因_JVM崩溃时:如何调查