@ConfigurationProperties 讲解[Spring] 深入浅出

本文详细介绍了如何在SpringBoot应用中从配置文件获取配置属性,包括使用@ConfigurationProperties注解,属性文件的命名规则,属性验证,以及自定义属性转换器。还对比了@ConfigurationProperties和@Value的区别。

1.前言

我们使用Spring框架的时候,经常会从配置文件中获取配置属性,比如发送邮件的时候,需要获取收发件人以及邮箱服务器地址和端口号。

那么本文将会介绍如何获取配置属性这样的小知识点,并做一定的延伸。

2.基础

        2.1我们先在pom.xml中添加以下依赖项: spring-boot-starter-parent
<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.7</version>
		<relativePath/>
</parent>

        spring-boot-starter-validation 用于验证一些属性

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.2 定义配置类

官方文档建议我们最好将需要定义的属性分离出来,放在单独的POJO类里。

下面我们开始定义一个配置类:

package com.jay.mydemo.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "mail")
public class MailConfigProperties {

    private String hostName;
    private int port;
    private String from;

    // default getters and setters
}

我们使用了@Configuration,这样Spring就会在应用上下文中创建一个对应的Spring Bean。如果我们不使用这个注解,也可以在Application类中添加以下注解。

@EnableConfigurationProperties(MailConfigProperties.class)

@ConfigurationProperties中定义了前缀mail。Spring会自动将POJO类与属性文件中前缀为mail的属性绑定。

Spring对绑定的属性比较宽松,比如以下的属性名都会绑到hostName上:

mail.hostName
mail.hostname
mail.host_name
mail.host-name
mail.HOST_NAME
mail.host-name

我们可以使用下面这个简单的属性文件来对应POJO类: 

mail.hostname=smtp.163.com
mail.port=25
mail.from=jay.xu@example.com

当然,从Spring Boot 2.2开始,我们已经不再需要使用@Component、@Configuration来注释配置类,同样也不需要@EnableConfigurationProperties。 

Configuration properties scanning was enabled by default in Spring Boot 2.2.0 but as of Spring Boot 2.2.1 you must opt-in using @ConfigurationPropertiesScan.

因为Spring会通过类路径的扫描自动注册@ConfigurationProperties类。

你需要做的是在Application类中使用@ConfigurationPropertiesScan注解来扫描配置类的包地址,如:

@ConfigurationPropertiesScan("com.jay.mydemo.config")
2.3 属性嵌套 

我们创建一个MailCrendential类:

package com.jay.mydemo.config;

@Data
public class MailCrendential {

    private String username;

    private String password;

}

然后再更新MailConfigProperties类,在其中我们加入一个List、一个Map以及MailCrendential。

private List<String> recipients;
private Map<String, String> headers;
private MailCrendential mailCrendential;

 与此同时,我们更新application.properties:

# mail props
## basic
mail.hostname=smtp.163.com
mail.port=25
mail.from=jay.xu@example.com
## recipient list
mail.recipients[0]=recipients0@example.com
mail.recipients[1]=recipients1@example.com

## header map
mail.headers.redelivery=true
mail.headers.secure=true

## object
mail.mailCrendential.username=jayxu
mail.mailCrendential.password=password

做到这一步,我们可以尝试创建一个测试类来打印看看配置是否无误。

package com.jay.mydemo;

import com.jay.mydemo.config.MailConfigProperties;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MydemoApplicationTests {

	@Autowired
	MailConfigProperties mailConfigProperties;

	@Test
	void testMailConfigProperties() {
		System.out.println(mailConfigProperties);
	}

}

 输出如下:

MailConfigProperties{hostName='smtp.163.com', port=25, from='jay.xu@example.com', recipients=[recipients0@example.com, recipients1@example.com], headers={redelivery=true, secure=true}, mailCrendential=MailCrendential{username='jayxu', password='password'}}
2.4 使用在@Bean方法上 

除了上面的用法之外,当我们用到第三方类或者无法直接改动原有的类,我们可以将@ConfigurationProperties用在@Bean注解的方法上。

请看这个例子,先创建一个"第三方类":

package com.jay.mydemo.config;

public class ThirdPartyItem {

    private String name;

    private String description;

    // default getters and setters
}

然后我们创建一个配置类用来存放这些“第三方类”的Bean:

package com.jay.mydemo.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConfigProperties {

    @Bean
    @ConfigurationProperties(prefix = "thirdpartyitem")
    public ThirdPartyItem thirdPartyItem(){
        return new ThirdPartyItem();
    }
}

 这样我们就可以在无法修改第三方类的情况下依然可以将其作为配置Bean使用。

3.进阶

3.1 属性验证

我们可以在配置类中加入属性验证。

@Configuration
@ConfigurationProperties(prefix = "mail")
@Validated
public class MailConfigProperties {

    @NotBlank
    private String hostName;

    @Min(1048)
    @Max(6475)
    private int port;

    @Pattern(regexp = "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,6}$")
    private String from;
    // . . .
}

这样的验证使得代码更加简洁,如果验证失败,那么应用就会启动失败,会报下面的错

**************************
APPLICATION FAILED TO START
***************************

Description:

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'mail' to com.jay.mydemo.config.MailConfigProperties$$EnhancerBySpringCGLIB$$89aa73c0 failed:

    Property: mail.hostName
    Value: 
    Origin: class path resource [application.properties] - 4:0
    Reason: 不能为空
3.2 属性转换 
  1. Duration类

我们可以看一个Duration类的例子。 首先我们定义一个含有Duration类型字段的配置类:

package com.jay.mydemo.config;

import java.time.Duration;

@ConfigurationProperties(prefix = "conversion")
public class ConversionConfigProperties {

    private Duration defaultTime;

    private Duration nanoTime;

    // default getters and setters
}

 属性文件中加入以下行:

# duration
conversion.defaultTime=8
conversion.nanoTime=8ns

打印出结果:

ConversionConfigProperties{defaultTime=PT0.008S, nanoTime=PT0.000000008S}

 所以ConversionConfigProperties里已经包含了8毫秒,8纳秒。除此之外,d,h,m,s也支持,分别代表天,小时,分钟,秒。

当然你也可以不写时间,用上@DurationUnit即可:

@DurationUnit(ChronoUnit.HOURS)
private Duration hourTime;

对应的属性文件:

duration.hourTime=8

这样做是不是很方便,省得你数着指头做时间换算了。

类似于Duration的还有DataSize,你可以用它来方便地定义数据大小——B,KB,MB,GB,TB,用法在此不再赘述。

3.3 自定义属性转换器

 

我们也可以自定义一个属性转换器——将属性转换成一个指定class。

我们先来创建一个简单类Book:

package com.jay.mydemo.config;

public class Book {
    
    private String name;
    
    private double price;
    
    private String description;
    
    // default getters and setters
}

 在属性文件中我们添加下面这行

conversion.book=java,88.00,java programming

 并且在ConversionConfigProperties加上

private Book book;

然后我们来定义自己的属性转换器:

实现Converter并且使用@ConfigurationPropertiesBinding注解。

package com.jay.mydemo.config;

import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
@ConfigurationPropertiesBinding
public class BookConverter implements Converter<String, Book> {

    @Override
    public Book convert(String source) {
        String[] data = source.split(",");
        Book book = new Book();
        book.setName(data[0]);
        book.setPrice(Double.parseDouble(data[1]));
        book.setDescription(data[2]);
        return book;
    }
}

 

这样自定义的属性转换器便大功告成了!

我们可以测试一下,打印出以下结果:

Book{name='java', price=88.0, description='java programming'}
 3.4 @ConfigurationProperties与@Value比较

除了@ConfigurationProperties注解可以获取配置文件中属性值,我们还可以使用@Value来一个一个地注解字段。

请看示例:

package com.jay.mydemo.config;

import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
@ConfigurationPropertiesBinding
public class BookConverter implements Converter<String, Book> {

    @Override
    public Book convert(String source) {
        String[] data = source.split(",");
        Book book = new Book();
        book.setName(data[0]);
        book.setPrice(Double.parseDouble(data[1]));
        book.setDescription(data[2]);
        return book;
    }
}

复制代码

这样自定义的属性转换器便大功告成了!

我们可以测试一下,打印出以下结果:

Book{name='java', price=88.0, description='java programming'}
复制代码

3.4 @ConfigurationProperties与@Value比较

除了@ConfigurationProperties注解可以获取配置文件中属性值,我们还可以使用@Value来一个一个地注解字段。

请看示例:

package com.jay.mydemo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.util.List;
import java.util.Map;

@Configuration
public class MailConfigProperties2 {

    // SpEL表达式
    @Value("#{ '${mail.hostname}'.length() > 0 ? '${mail.hostname}' : 'smtp.163.com'}")
    private String hostName;

    // 字面量
    @Value("25")
    private int port;

    // 属性key
    @Value("${mail.from}")
    private String from;

    // default getters and setters
}

在这个改写的MailConfigProperties2中,我们可以看出@Value的常用方式,下表是两个注解之间的主要区别:

@ConfigurationProperties@Value
注入配置文件中的属性批量单个指定
松散语法支持不支持
SpEL不支持支持
JSR303数据校验支持不支持
复杂类型封装支持不支持

如果我们需要使用SpEL表达式,我们可以使用@Value。除此之外,我们都应该使用@ConfigurationProperties, 它更方便全能。

即使是只是在某个业务逻辑中偶尔使用一次来获取配置信息,也推荐使用@ConfigurationProperties,因为@Value零散,不易管理,注解属性的时候还要保证前缀和属性名书写无误。

 

 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值