当前位置: 主页 > JAVA语言

java 动态加载properties-java 动态加载class

发布时间:2023-02-12 10:17   浏览次数:次   作者:佚名

在编写项目代码时,我们需要更灵活的配置和更好的模块化集成。在Spring Boot项目中,为了满足上述需求,我们在application.properties或application.yml文件中配置了大量的参数。 通过@ConfigurationProperties注解,我们可以很方便的获取到这些参数值

使用@ConfigurationProperties 配置模块

假设我们正在构建一个发送电子邮件的模块。 对于本地测试,我们不希望模块真正发送邮件,所以我们需要一个参数来“切换”禁用该功能。另外,我们要为这些邮件配置一个默认主题,这样当我们查看邮件时收件箱,我们可以通过邮件的主题快速判断这是一封测试邮件

在 application.properties 文件中创建这些参数:

java代码动态加载_java 动态加载class_java 动态加载properties

我们可以使用@Value 注解或者使用 Spring Environment bean 来访问这些属性,但是这种注入配置的方式有时比较麻烦。我们将使用更安全的方式(@ConfigurationProperties)来获取这些属性

java代码动态加载_java 动态加载class_java 动态加载properties

@ConfigurationProperties 的基本用法非常简单:我们为要捕获的每个外部属性提供一个带有字段的类。 请注意以下几点:

Spring松散绑定规则(relaxed binding)

Spring 使用一些松散的规则来绑定属性。 因此,以下变体都将绑定到 hostName 属性:

java 动态加载class_java 动态加载properties_java代码动态加载

如果我们将一个 MailModuleProperties 类型的 bean 注入到另一个 bean 中,这个 bean 现在可以以类型安全的方式访问那些外部配置参数的值。

但是,我们仍然需要让 Spring 知道我们的 @ConfigurationProperties 类存在,以便将其加载到应用程序上下文中(面试官还不知道 BeanFactory 和 ApplicationContext 之间的区别吗?)

激活@ConfigurationProperties

对于Spring Boot,创建​​一个MailModuleProperties类型的bean,我们可以通过以下方式添加到应用上下文中

首先,我们可以通过添加@Component注解让Component Scan进行扫描

java代码动态加载_java 动态加载class_java 动态加载properties

很明显,只有当类所在的包被Spring@ComponentScan注解扫描到时才会生效。 默认情况下,此注解将扫描主应用程序类下的所有包结构

我们也可以通过 Spring 的 Java Configuration 特性来达到同样的效果:

java 动态加载class_java 动态加载properties_java代码动态加载

只要 MailModuleConfiguration 类被 Spring Boot 应用程序扫描到,我们就可以在应用程序上下文中访问 MailModuleProperties bean

我们还可以使用 @EnableConfigurationProperties 注释让我们的类为 Spring Boot 所知。 在这个注解中,实际上使用了@Import(EnableConfigurationPropertiesImportSelector.class)。 你可以看看

java 动态加载properties_java代码动态加载_java 动态加载class

激活 @ConfigurationProperties 类的最佳方式是什么?

以上所有方法都同样有效。 但是,我建议将您的应用程序模块化,并让每个模块都提供自己的 @ConfigurationProperties 类,只提供它需要的属性,就像我们在上面的代码中为邮件模块所做的那样。 这使得在不影响其他模块的情况下重构一个模块中的属性变得容易。

因此,不是在应用程序类本身上使用@EnableConfigurationProperties,如许多其他教程中所示,而是在模块特定的@Configuration 类上使用@EnableConfigurationProperties,它也可以利用对应用程序的包私有可见性其余的隐藏特性。

无法转换的财产

如果我们在 application.properties 属性上定义的属性没有被正确解析会怎样? 假设我们为一个应该是布尔值的属性提供了一个值“foo”:

java 动态加载properties_java 动态加载class_java代码动态加载

默认情况下,Spring Boot 会启动失败并抛出异常:

Failed to bind properties under 'myapp.mail.enabled' to java.lang.Boolean:
    Property: myapp.mail.enabled
    Value: foo
    Origin: class path resource [application.properties]:1:20
    Reason: failed to convert java.lang.String to java.lang.Boolean

当我们为该属性配置了错误的值,又不想让Spring Boot应用启动失败时,我们可以将ignoreInvalidFields属性设置为true(默认为false)

java 动态加载properties_java代码动态加载_java 动态加载class

这样Spring Boot就会把enabled字段设置为我们在Java代码中设置的默认值。 如果我们不设置默认值,enabled 将为 null,因为这里定义了包装类 Boolean

未知属性

与上述情况有些相反,如果我们在 application.properties 文件中提供 MailModuleProperties 类不知道的属性,会发生什么情况?

java 动态加载properties_java 动态加载class_java代码动态加载

默认情况下,Spring Boot 会忽略无法绑定到 @ConfigurationProperties 类字段的属性

但是,当配置文件中有一个属性实际上没有绑定到@ConfigurationProperties类时,我们可能希望启动失败。也许我们之前使用过这个配置属性,但它已经被移除了,在这种情况下我们想要触发以从 application.properties 中手动删除此属性

为了实现上述情况,我们只需要将ignoreUnknownFields属性设置为false即可(默认为true)

java 动态加载class_java 动态加载properties_java代码动态加载

现在,当应用程序启动时,控制台将向我们提供异常信息

Binding to target [Bindable@cf65451 type = com.example.configurationproperties.properties.MailModuleProperties, value = 'provided', annotations = array[@org.springframework.boot.context.properties.ConfigurationProperties(value=myapp.mail, prefix=myapp.mail, ignoreInvalidFields=false, ignoreUnknownFields=false)]] failed:
    Property: myapp.mail.unknown-property
    Value: foo
    Origin: class path resource [application.properties]:3:29
    Reason: The elements [myapp.mail.unknown-property] were left unbound.

弃用警告⚠️(弃用警告)

ignoreUnknownFields在以后的Spring Boot版本中会被标记为deprecatedjava 动态加载properties,因为我们可能有两个类都绑定了@ConfigurationProperties同一个命名空间(namespace),其中一个类可能知道一个属性,另一个类不知道某个属性,会导致启动失败

在启动时验证@ConfigurationProperties

如果我们希望配置参数在传入应用时有效,可以在字段中添加bean验证注解,在类中添加@Validated注解

java 动态加载properties_java代码动态加载_java 动态加载class

如果我们忘记在 application.properties 文件中设置 enabled 属性,并将 defaultSubject 设置为空

java 动态加载properties_java代码动态加载_java 动态加载class

当应用程序启动时,我们会得到 BindValidationException

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'myapp.mail' to com.example.configurationproperties.properties.MailModuleProperties failed:
    Property: myapp.mail.enabled
    Value: null
    Reason: must not be null
    Property: myapp.mail.defaultSubject
    Value: null
    Reason: must not be empty

当然这些默认的验证注解不能满足你的验证需求,我们也可以自定义注解

如果你的验证逻辑很特殊,我们可以实现一个方法,用@PostConstruct标记。 如果验证失败,该方法将抛出异常。 关于@PostConstruct,可以查看Spring Bean的生命周期。 我从哪里来?

复杂属性类型

在大多数情况下,我们传递给应用程序的参数是基本的字符串或数字。但是,有时我们需要传递数据类型java 动态加载properties,例如 List

列表和集合

如果,我们为邮件模块提供 SMTP 服务列表,我们可以将此属性添加到 MailModuleProperties 类

java代码动态加载_java 动态加载properties_java 动态加载class

我们有两种方式让Spring Boot自动填充列表属性

应用程序.properties

在application.properties文件中以数组的形式写入

java代码动态加载_java 动态加载class_java 动态加载properties

应用.yml

YAML本身支持list类型,所以可以在application.yml文件中添加:

java 动态加载properties_java代码动态加载_java 动态加载class

set集合也是这样配置的,不再重复写。另外YAML是一种比较好的阅读方式,层次分明,所以建议大家在实际应用中使用这种方式进行数据配置

期间

Spring Boot内置支持从配置参数中解析持续时间(duration),给出明确的指示

java代码动态加载_java 动态加载properties_java 动态加载class

我们可以配置毫秒值和带有单位的文本:

java 动态加载class_java代码动态加载_java 动态加载properties

官网已经明确说明配置时长不指定单位,默认指定毫秒。 我们也可以通过@DurationUnit来指定单位:

java 动态加载class_java 动态加载properties_java代码动态加载

常用单位如下:

数据大小

和Duration的用法一样,默认单位是字节(byte),可以通过@DataSizeUnit指定单位:

java 动态加载properties_java 动态加载class_java代码动态加载

添加配置

java 动态加载class_java 动态加载properties_java代码动态加载

但是我测试的时候,打印出来的结果都是B(字节)显示的

常用单位如下:

自定义类型

在某些情况下,我们希望将配置参数解析为我们自定义的对象类型。 假设,我们设置最大包裹重量:

java代码动态加载_java 动态加载class_java 动态加载properties

在 MailModuleProperties 中添加 Weight 属性

java 动态加载properties_java代码动态加载_java 动态加载class

我们可以模仿DataSize和Duration来创建自己的转换器(converter)

java 动态加载class_java 动态加载properties_java代码动态加载

在 Spring Boot 上下文中注册它

java代码动态加载_java 动态加载class_java 动态加载properties

@ConfigurationPropertiesBinding注解是为了让Spring Boot知道使用转换器进行数据绑定

使用Spring Boot Configuration Processor完成自动补全

我们在项目中添加依赖:

行家

java 动态加载properties_java 动态加载class_java代码动态加载

摇篮

java 动态加载properties_java代码动态加载_java 动态加载class

重建项目后,配置处理器将为我们创建一个 JSON 文件:

java 动态加载properties_java代码动态加载_java 动态加载class

这样,当我们在application.properties和application.yml中写配置的时候,就会有自动提示:

java代码动态加载_java 动态加载properties_java 动态加载class

将配置属性标记为已弃用

配置处理器允许我们将属性标记为已弃用

java 动态加载class_java 动态加载properties_java代码动态加载

我们可以通过在该字段的getter方法中添加@DeprecatedConfigurationProperty注解将该字段标记为弃用,重新构建项目,看看JSON文件发生了什么?

java代码动态加载_java 动态加载properties_java 动态加载class

当我们再次编写配置文件时,已经给出了明确的deprecated提示:

java 动态加载properties_java代码动态加载_java 动态加载class

总结

Spring Boot 的@ConfigurationProperties 注解在绑定类型安全的Java Bean 时非常强大。 我们可以配合它的注解属性和@DeprecatedConfigurationProperty注解,获得更友好的编程方式,让我们的配置更加模块化。

附加信息

认为@ConfigurationProperties 注解满足了我们所有的需求? 事实上,并非如此。 Spring官网明确给出了这个注解和@Value注解的对比:

java代码动态加载_java 动态加载properties_java 动态加载class

如果使用SpEL表达式,我们只能选择@Value注解

另外,我在阅读RabbitMQ源码的时候,发现RabbitProperties类充分利用了@ConfigurationProperties注解的特性:

感觉自己是后知后觉,最近一直在想小时候为什么要背诵古诗词、文言文等经典,因为这样我在写作的时候就可以轻松熟练地引用经典文章。技术也是如此。 各种框架的源码都是学生时代的古诗词和文言文。 我们需要多多查阅多读,甚至背诵编程思想,这样才能写出越来越优雅的代码。

关于@ConfigurationProperties注解的使用,这里推荐RabbitMQ Github源码,看看这个类,就知道如何充分利用这个注解了。

demo代码获取,回复公众号“demo”,打开链接可查看对应子文件夹

灵魂探索 在实际项目中,你能否充分利用这些特性,让你的配置更加灵活和模块化? 在阅读框架源码时,它们是如何配置的? @Value 注解如何给出默认值?生产力工具

java代码动态加载_java 动态加载class_java 动态加载properties

[中心]