摆脱困境:将属性值注入配置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();}
}

这是一个问题,因为

  1. 因为我们是人类,所以我们会打错字 。 这不是一个大问题,因为在启动应用程序时我们会注意到它。 但是,它使我们放慢了速度。
  2. 这使维护更加困难 。 如果更改属性的名称,则必须对使用该属性的每个类进行此更改。

我们可以通过将属性名称移到常量类来解决此问题。 如果这样做,我们的源代码如下所示:

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.添加验证逻辑成为问题

假设我们有两个类( AB ),它们需要app.server.protocol属性的值。 如果我们将此属性值直接注入到AB Bean中,并且想要确保该属性的值为'http'或'https',则必须

  1. 将验证逻辑添加到两个bean类中。
  2. 将验证逻辑添加到实用程序类中,并在需要验证是否给出了正确的协议时使用它。

如果我们将验证逻辑添加到两个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));}}
}

这是一个维护问题,因为AB类包含复制粘贴代码。 通过将验证逻辑移至实用程序类并在创建新的AB对象时使用它,可以稍微改善这种情况。

完成此操作后,我们的源代码如下所示:

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

让我们继续并配置示例应用程序的应用程序上下文。

配置应用程序上下文

我们的示例应用程序的应用程序上下文配置类有两个目标:

  1. 启用S​​pring MVC并导入其默认配置。
  2. 确保读取了从application.properties文件中找到的属性值,并且可以将其注入Spring Bean中。

通过执行以下步骤,我们可以实现其第二个第二个目标:

  1. 配置Spring容器以扫描所有包含bean类的软件包。
  2. 确保从application.properties文件中找到的属性值已读取并添加到Spring Environment中
  3. 确保从@Value批注中找到的$ {...}占位符替换为从当前Spring Environment及其PropertySources中找到的属性值。

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类。 我们可以按照以下步骤进行操作:

  1. 创建WebProperties类,并使用@Component注释对其进行注释。
  2. 将最终协议serverHostserverPort字段添加到创建的类中。
  3. 通过使用构造函数注入将属性值注入到这些字段中,并确保协议字段的值必须为'http'或'https'(忽略大小写)。
  4. 添加用于获取实际属性值的吸气剂。

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类。 我们可以按照以下步骤进行操作:

  1. 创建ApplicationProperties类,并使用@Component注释对其进行注释。
  2. 将最终名称productionModeEnabledwebProperties字段添加到创建的类。
  3. 通过使用构造函数注入,将属性值和WebProperties bean注入ApplicationProperties bean。
  4. 添加用于获取字段值的吸气剂。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360197.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js编码java解码_js编码

虽然escape()、encodeURI()、encodeURIComponent()三种方法都能对一些影响URL完整性的特殊字符进行过滤。但后两者是将字符串转换为UTF-8的方式来传输,解决了页面编码不一至导致的乱码问题。例如:发送页与接受页的编码格式(Charset)不一致(假设发送页面是…

淘宝IP地址查询

官方网址:http://ip.taobao.com/index.php 相关文章: http://www.cnblogs.com/zetee/p/3482085.html http://www.cnblogs.com/zetee/p/3481297.html转载于:https://www.cnblogs.com/AloneSword/p/3483176.html

mysql查询各专业人数_查询各专业的学生数。

【简答题】查询“数据库”不及格的学生的学号和成绩。【判断题】螺位错可以攀移。【单选题】Internet 的核心协议是()【单选题】多媒体计算机是指()【单选题】码组10100与10001之间的码距为( )。【简答题】查询选修了“计算机基础”课程的学生学号和成绩。【单选题】小强用数码…

[SharePoint][SharePoint2013循序渐进]SPS2013简介

本章概要: 1、啥是SPS2013 2、SharePoint如何作用于团队协作和信息共享 3、SP2013有哪些用户权限 4、什么是SharePoint2013 online 5、SP在内部署和在线订阅计划的区别 6、office集成 7、与Microsoft Dynamics AX集成提供一个web前端界面 【什么是SharePoint2013】 …

python怎么改目录_如何查看文件,而不是使用Python进行更改的目录?

问题:How do I watch a file for changes using Python?建议使用看门狗,但我发现它只能观看目录,而不是文件.watchdog-test.py是看门狗的示例脚本:$python watchdog-test.py ab_test_res.sh &[1] 30628fbtfbt64:~/laike9m$Traceback (most recent c…

javascript arguments

此文为转载文章: 什么是arguments arguments 是是JavaScript里的一个内置对象,它很古怪,也经常被人所忽视,但实际上是很重要的。所有主要的js函数库都利用了arguments对象。所以agruments对象对于javascript程序员来说是必需熟悉的…

hutool 自定义excel_Hutool Java 工具类库导出 Excel,超级简单!

前言在开发应用系统的时候,导出文件是必不可放的功能。以前用过POI、easyexcel等工具的导入导出功能,但总感觉太麻烦了,代码特别多,感觉并不是很好用。今天给大家介绍一款新工具,java工具类库Hutool。Hutool简介Hutool…

带头结点头部插入创建链表

/*创建一个带头结点的链表&#xff0c;头部插入法创建*/ #include<stdio.h> #include<stdlib.h>#define DataType chartypedef struct node {DataType data;struct node *next; }ListNode,*LinkList;/*创建一个带头结点的链表*/ LinkList createLinkList() {char c…

Java性能调优:充分利用垃圾收集器

JVM背后发生了什么&#xff0c;垃圾回收如何影响Java性能&#xff1f; 性能调优世界是一个危险的地方&#xff0c;一个JVM标志失衡&#xff0c;事情很快就会变得繁琐。 因此 &#xff0c;我们决定求助于Java性能调优专家&#xff0c; 单调 JVM探查器mjprof的创建者Haim Yadid …

[单选]物联网产业链的主要产品不包括下列哪一项 - 关于物联网(主讲:柳毅)笔记...

[单选]物联网产业链的主要产品不包括下列哪一项 转载于:https://www.cnblogs.com/scgw/p/3488452.html

java web读取excel_JavaWeb使用POI操作Excel文件实例

1.为项目添加POI点进去之后下载(上边的是编译好的类&#xff0c;下边的是源代码)解压文件夹&#xff0c;把下面三个文件复制到WebComtent>WEB-INF>lib文件夹下再把这三个文件复制到Tomcat的lib文件夹下&#xff0c;否则Tomcat会因为找不到类而报错(这个地方郁闷了一上午)…

架构大型企业Java项目–我的虚拟JUG会话

昨天我很荣幸被邀请参加虚拟JUG 。 这是一个很大的荣誉&#xff0c;其原因有很多&#xff1a;首先&#xff0c;我是vJUG董事会的一员&#xff0c;其次&#xff0c;因为这是我第二次向这个对Java感兴趣的伟大团队做演讲。 被邀请回来总是很高兴的。 架构大型企业Java项目 过去&…

javascript操作cookie

1. 什么是cookie&#xff1f; Cookie就是浏览器保存在计算机上面的一下信息。一般是保存用户登录状态&#xff0c;就是说Cookie会保存用户登录某个网站的信息在本地&#xff0c;下次这个用户访问网站的时候会自动取出他的Cookie信息&#xff0c;方便定制用户想要的内容。2. 实例…

java 静态方法与实例方法的区别_静态方法与实例方法的区分

Java系列之&#xff1a;看似简单的问题 静态方法和实例化方法的区别Java程序启动class文件被读取时类被加载&#xff0c;如果有static方法&#xff0c;此时会分配内存&#xff0c;非static方法实例化类时才在内存中分配控件存储&#xff0c;引用存储在堆栈中&#xff0c;实体存…

使用wrapper将java程序注册程windows服务后不生效

使用wrapper将java程序注册程windows服务后不生效 使用add.bat或test***.bat测试通过了&#xff0c; 然后使用install***.bat注册后cmd显示注册成功。 但是程序到了运行时间就是不运行&#xff01;&#xff01; 解决办法 控制面板 ---> 管理工具 ---> 服务&#xff0c;找…

有简历,为何还要自我介绍?

有简历&#xff0c;为何还要自我介绍&#xff1f; 一个常规的面试&#xff0c;寒暄之后考官提出的第一个面试问题几乎千篇一律&#xff1a;“请你简单地做一下自我介绍”。有些被面试者都会问&#xff1a;简历中情况已经写得很清楚了&#xff0c;这是否多此一举&#xff1f; 要…

java aio聊天_JAVA aio简单使用

使用aio&#xff0c;实现客户端和服务器 对一个数进行轮流累加//服务器端public class Server {private static ExecutorService executorService Executors.newFixedThreadPool(4);public static void main(String[] args) {try {AsynchronousChannelGroup groupAsynchronous…

使用jOOQ的MockDataProvider破解简单的JDBC ResultSet缓存

某些查询不应该一直访问数据库。 例如&#xff0c;当您查询主数据 &#xff08;例如系统设置&#xff0c;语言&#xff0c;翻译等&#xff09;时&#xff0c;您可能希望避免一直通过网络发送相同的愚蠢查询&#xff08;和结果&#xff09;。 例如&#xff1a; SELECT * FROM l…

ASP.NET内置对象

Request[从浏览器获取信息] QueryString:获取HTTP查询字符串变量集合 Path:获取当前请求的虚拟路径 UserHostAddress:获取远程客户端的IP主机地址 Browser:获取有关正在请求的客户端浏览器功能的信息 Form:获取窗体变量的集合 Url:获取当前请求的URL信息 MapPath():返回与Web服…

java爬虫新闻网站_java爬虫 之 搜狐新闻爬虫(一)

最近开始学习java爬虫&#xff0c;网上很多教程&#xff0c;自己找的时候花了好久的时间去理解别人的思路。打算将自己最近的学习进度稍作整理&#xff0c;理清思路。主要工具使用jsoup&#xff1a;具体用法看http://blog.csdn.net/u012315428/article/details/51135640下面是获…