SpringBoot类型安全配置

一个使用@Value(${...})的例子

下面是一段简单的服务器配置的代码:

@Data
@Component
public class ServerConfig {

    /** 是否启用服务 */
    @Value("${ali.server.enabled}")
    private boolean enabled;
    /** 服务器的远程地址 */
    @Value("${ali.server.remote-address}")
    private InetAddress remoteAddress;
    /** 服务器的安全配置 */
    @Autowired
    private Security security;

    @Component
    @Data
    public static class Security {

        /** 服务器的用户名 */
        @Value("${ali.server.security.username}")
        private String username;
        /** 用户密码 */
        @Value("${ali.server.security.username}")
        private String password;
        /** 角色列表 */
        @Value("${ali.server.security.roles}")
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
    }
}

这段代码定义了如下的配置key:

key 类型 描述
server.enabled boolean 是否启用服务
server.remote-address InetAddress 绑定的ip地址
server.security.username String 安全用户名
server.security.password String 安全密码
server.security.roles List<String> 安全角色

Spring提供的ConversionService会将这些key的字符串配置值自动转换为对应的类型

另外@Value注解还支持SpEL表达式,可以说为Spring项目提供了强大的外部配置绑定能力。但对于复杂配置的管理能力不足:

  1. 基于字符串的表达式没有编译时类型检查
  2. 多处引用配置key时,需要重复引用字面量,容易出错
  3. 不容易重构,对key的命名变更
  4. 对复杂的对象配置比较困难

看上面的例子中Security需要声明为Bean才能支持@Value绑定,然后注入到Server中。如果是更复杂的场景,管理Bean会更困难

  1. 没有配置的元信息,想全局查看配置比较困难,编写配置文件时也没有任何可靠这自动校验很容易写错配置项

SpringBoot提供的类型安全配置方案

SpringBoot提供了@ConfigurationProperties注解,提供类型安全的配置声明方案。

利用@ConfigurationProperties修改上面的例子:

@Data
@Component
@ConfigurationProperties("ali.server")
public class ServerConfig {

    /** 是否启用服务 */
    private boolean enabled;
    /** 服务器的远程地址 */
    private InetAddress remoteAddress;
    /** 服务器的安全配置 */
    private Security security = new Security();

    @Data
    public static class Security {

        /** 服务器的用户名 */
        private String username;
        /** 用户密码 */
        private String password;
        /** 角色列表 */
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
    }
}

上述代码定义的配置key与使用@Value注解的例子是相同的,但具有如下特点:

  1. 代码更简单,不依赖key的字面量
  2. Security不需要声明为Bean,不需要注入到Server,可以支持更复杂的声明
  3. 结合spring-boot-configuration-processor提供的注解处理器可以自动生成配置的元信息

元信息中包含key的命名及从javadoc中读取的说明信息以及默认值。

有了这些元信息,在编写配置文件是使用IDEA编辑器可以自动弹出提示

SpringBoot类型安全配置

有了编辑的辅助,相对不容易写错配置,且可以很容易看到看有哪些配置可以使用

因为有了这些元信息,IDEA的重构(重命名)命令可以自动做全局重构

  1. 避免使用字面量,不易出错,鼓励使用Bean注入来使用配置信息
  2. 多处引用Bean来读取配置时,都是Java代码级别的引用有编译时检查

ConfigurationProperties支持Spring Validation

@Data
@Component
@Validated
@ConfigurationProperties("ali.server")
public class Server {

    /** 是否启用服务 */
    private boolean enabled;
    /** 服务器的远程地址 */
    @NotNull
    private InetAddress remoteAddress;
    /** 服务器的安全配置 */
    private Security security = new Security();

    @Data
    public static class Security {

        /** 服务器的用户名 */
        @NotBlank
        private String username;
        /** 用户密码 */
        @Pattern(regexp = "\\w+\\d+")
        private String password;
        /** 角色列表 */
        @Size(min = 1, max = 10)
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
    }
}

利用Spring Validation集成,可以对配置提供灵活强大的校验能力。

支持第三方组件配置

如果依赖一个需要配置的第三方组件,要如何为其编写外部依赖?下面以DruidDataSource为例:

首先使用@Value来实现,那么代码如下:

@Data
@Component
public class DataSourceConfig {
    @Value("db.url")
    private String url;
    @Value("db.username")
    private String username;
    @Value("db.password")
    private String password;
    @Value("db.driver-class-name")
    private String driverClassName;
}

@Configuration
@ComponentScan
public class DataSourceConfiguration {

    @Bean
    public DruidDataSource dataSource(DataSourceConfig config) {
        DruidDataSource ds = new DruidDataSource();
        ds.setUrl(config.getUrl());
        ds.setUsername(config.getUsername());
        ds.setPassword(config.getPassword());
        ds.setDriverClassName(config.getDriverClassName());
        return ds;
    }
}
db.url=jdbc://....
db.username=root
db.password=....
db.driver-class-name=DriverClassName

上述代码具有如下特点:

  1. 需要一个额外的配置类来做值绑定
  2. 创建DruidDataSource实例时需要做属性拷贝(可以使用BeanUtils.copyProperties替代)
  3. 灵活性差,增加配置需要修改DataSourceConfig类

加入现在要配置数据源的最大连接数,那么需要给DataSourceConfig类增加一个maxActive属性

下面使用@ConfigurationProperties来改写,代码如下:

@Configuration
public class DataSourceConfiguration {

    @Bean
    @ConfigurationProperties("db")
    public DruidDataSource dataSource() {
        return new DruidDataSource();
    }
}

SpringBoot会自动识别DruidDataSource的所有属性,自动绑定到约定的key上。

例如有DruidDataSource.setMaxActive定义了一个maxActive属性,那么可以使用db.max-active来对其进行配置。

所以这段代码可以使用与上面使用@Value注解时同样的配置进行初始化。

上一篇:Go语言之 unsafe 包之内存布局


下一篇:深入MongoDB内存溢出调优