SpringBoot-ConfigurationProperties

写代码时,常会在代码中使用配置相关的常量。

对于这些配置常量,最佳实践是配置文件分离,即将这些配置常量写入一个配置文件中。常见的配置文件包括如properties、XML文件、JSON文件、YAML文件。这样每次修改配置时,不需要改动源文件并重新编译,也不需要通过命令行传参,只需改动配置文件中对应属性即可。而且把配置信息放在一起会使代码可读性,可维护性更好。

Java中的配置文件

Java的常用配置文件为properties文件。可以通过FileInputStreamClassLoader来读取配置文件。并通过包装成Properties对象,来解析文件中的属性。由于Java的配置文件通常位于类路径下,因此使用ClassLoader来读取有更好的可移植性。

1
2
3
4
5
6
7
8
9
10
11
public Properties loadProperties(String fileName) throws IOException {
Properties properties = new Properties();
// 使用ClassLoader获取配置文件的输入流
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
if (inputStream == null) {
throw new IOException("Unable to find " + fileName + " in classpath");
}
properties.load(inputStream);
inputStream.close();
return properties;
}

之后需要将配置文件中的属性赋值到Java类的字段上。这种配置文件和类字段映射的思想叫做配置文件驱动开发

但在实际开发中,一整个项目的配置信息是十分多的。如果对于每一个配置属性都显式地赋值给一个类字段,那整个赋值过程会十分繁琐。因此需要一种方式,能将配置文件自动映射到类字段上。

ConfigurationProperties

谈到Java开发就离不开Spring(SpringBoot)。Spring提供了许多常见且通用的功能和工具来简化 Java 应用程序的开发,例如上文提到的配置文件的自动映射。

在Spring当中,如果需要将配置文件映射到某个类上,那么只需要在这个类上添加@ConfigurationProperties注解,并将这个类在Spring容器中实例化,Spring就会自动完成配置属性注入的功能。

具体实现时,通常有两种方式。

  1. @ConfigurationProperties + @Component

  2. @ConfigurationProperties + @EnableConfigurationProperties

其中@ConfigurationProperties 加在需要配置属性注入的类上,并在注解中指定配置属性的前缀。并通过@Component 将类加入到容器中。需要注意的是,通过这种方式需要确保在主配置类中开启了@ComponentScan,并且能扫描到此类。

而Spring的默认配置文件名为application.properties,且位于类路径的根目录下。这是Spring的默认大于配置软件设计原则。也就是如果没特殊情况,大家都遵守这样的约定。

1
2
3
abc.name=Tom
abc.version=V1.1
logging.level.root=info
1
2
3
4
5
6
7
@Component
@ConfigurationProperties(prefix = "abc")
@Data
public class MyConfiguration {
private String name;
private String version;
}

此处定义了MyConfiguration类,并且包含两个字段nameversion,字段名与配置文件中的属性键去掉前缀对应。并通过@ConfigurationProperties(prefix = "abc")注解指定了配置文件中的前缀,这样就可以在配置文件中通过前缀组织不同配置的属性。

或者,可以使用@EnableConfigurationProperties,添加在主配置类上,此时可以将属性注入的类上的@Component去掉,否则容器中会有两个MyConfiguration的Bean。

1
2
3
4
5
6
@ConfigurationProperties(prefix = "abc")
@Data
public class MyConfiguration {
private String name;
private String version;
}
1
2
3
4
5
6
7
@ComponentScan
@Configuration
@EnableConfigurationProperties(MyConfiguration.class)
public class MyConfig {
public static void main(String[] args) {
SpringApplication.run(MyConfig.class, args);
}

其中,@EnableConfigurationProperties中指定需要开启属性注入功能的类。

但在实际过程中,出现了一些问题。

遇到的问题

在实际学习过程中,属性注入一直失败。最后排查发现,对于属性注入的类,还需要配置Setter方法。这是因为配置属性注入,是通过Java反射,调用了对应的Setter方法。可以自行显示地定义Setter方法,或者使用@Data注解,自动生成类的Getter、Setter方法。

但在定义完Setter方法后,使用@ConfigurationProperties + @Component的方式配置属性注入依旧无法完成。通过对类属性打上断点,并查看调用栈,发现要使用这种方式完成配置属性注入,容器内必须要有ConfigurationPropertiesBindingPostProcessor。这是一个BeanPostProcessor,本质也是一个Bean,但和普通Bean的不同在于它可以参与Bean的实例化过程,以对其他Bean进行属性修改或功能增强。而在不使用@SpringBootApplication@EnableAutoConfiguration的最小配置启动下,默认没有向容器中导入这个BeanPostProcessor

ConfigurationPropertiesBindingPostProcessor

并且在调用栈中,可以观察到最后是通过反射的方式,调用Setter方法来完成了配置属性注入。

SetName

而使用@ConfigurationProperties + @EnableConfigurationProperties 的方式则不需要@EnableAutoConfiguration配置项。通过调试发现,@EnableConfigurationProperties 带有@Import(EnableConfigurationPropertiesRegistrar.class)注解,而EnableConfigurationPropertiesRegistrar在方法registerBeanDefinitions中,向容器中注册了ConfigurationPropertiesBindingPostProcessor,而后被实例化以提供配置属性注入的功能。

这一部分内容很涉及框架层面的内容,暂时不准备挖的更深。总之,@EnableConfigurationProperties 本身就会向容器内导入ConfigurationPropertiesBindingPostProcessor,因此不需要@EnableAutoConfiguration也能完成配置属性注入工作。

总结

配置文件分离是软件部署的最佳实践,熟悉其过程和原理也是每个程序员的必修课。在Spring框架中,使用@ConfigurationProperties 来完成配置文件与类的绑定,在使用时需要指定每个类对应配置文件键的前缀。并把这个类放入容器中,以被ConfigurationPropertiesBindingPostProcessor完成属性注入的功能。而ConfigurationPropertiesBindingPostProcessor可以通过@EnableAutoConfiguration@EnableConfigurationProperties导入。ConfigurationPropertiesBindingPostProcessor最终是通过Java反射完成属性注入,需要调用对应的Setter方法。


SpringBoot-ConfigurationProperties
http://dracoyus.github.io/2023/04/07/SpringBoot-ConfigurationProperties/
作者
DracoYu
发布于
2023年4月7日
许可协议