SpringBoot 实战:Spring Boot的配置绑定类Bindable居然如此强大

引言

      SpringBoot中极大的简化了项目中对于属性配置的加载方式,可以简单的通过 @Value, @ConfigurationProperties 来实现属性配置与Java POJO对象、Bean的成员变量的绑定,那如果出现一个某些场景,需要我们手动的、通过编程式的方式,将属性配置与给定的pojo对象进行绑定,我们又应该怎么实现呢?

 项目配置

首先搭建一个标准的SpringBoot项目工程,相关版本以及依赖如下

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

 代码演示

首先先给大家介绍下编程式的属性绑定,除了我们最熟悉的直接从 Environment 中获取配置之外,还可以使用 Binder来实现属性绑定。

// 获取binder实例
public static Binder get(Environment environment) {
    return get(environment, (BindHandler)null);
}
 
 
// 属性绑定
// Bind the specified target Class using this binder's property sources.
<T> BindResult<T> bind(String name, Class<T> target)
<T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler)
 
 
// Bind the specified target Class using this binder's property sources or create a new instance using the type of the Bindable if the result of the binding is null.
<T> T bindOrCreate(String name, Class<T> target)
<T> T bindOrCreate(String name, Bindable<T> target, BindHandler handler)

两种常见的使用方式:

  • bind方法: 将属性绑定到对应的类上, 不会返回null

  • bindOrCreate: 将属性绑定到对应的类上, 返回结果可能为null

直接将配置绑定在我们自定义的配置类上,也就是我们常用的方式 @ConfigurationProperties 注解来实现的方式:

demo:
    mail:
      host: smtp.163.com
      from: xhhuiblog@163.com
      username: test
      password: testpwd
      port: 465

接下来定义一个对应的属性配置类Mail

@Data
public class Mail {
    private String host;
    private String port;
    private String user;
    private String password;
    private String from;
}

使用方式:

@Component
public class BindHelper implements EnvironmentAware {
    private Environment environment;
 
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
 
 
    public void bindInfo() {
        // 直接将前缀对应的配置,加载到指定的对象中
        Binder binder = Binder.get(environment);
 
 
        // 直接绑定到配置类
        Mail mail = binder.bindOrCreate("demo.mail", Mail.class);
        System.out.println("mail = " + mail);
    }
 
}

在上面的基础使用方式之上,我们再加两个使用方式

  • 配置不存在时,返回什么?

  • 使用bind对于不存在时,如何表现

  • public void bindInfo() {
        // 直接将前缀对应的配置,加载到指定的对象中
        Binder binder = Binder.get(environment);
     
     
        // 直接绑定到配置类
        Mail mail = binder.bindOrCreate("demo.mail", Mail.class);
        System.out.println("mail = " + mail);
     
        mail = binder.bindOrCreate("demo.mail2", Mail.class);
        System.out.println("mail = " + mail);
     
        try {
            mail = binder.bind("demo.mail2", Mail.class).get();
            System.out.println("mail = " + mail);
        } catch (Exception e) {
            // 因为配置不存在,会报错
            System.out.println(e.getMessage());
        }
    }


执行之后,输出如下

从上面的输出可以看出,对于

  • bindOrCreat 而言,若整个配置不存在,返回一个空对象,内部属性为null;bind 若相关的配置不存在,会抛异常 (这个不存在指的是配置前缀demo.mail2的都没有)

  • 配置内的某个属性不存在,如 demo.mail.user 这个配置不存在时(配置中的是username),此时bind/bindOrCrate 返回的对象中,相关的属性是null (注意这种场景 bind 方法调用不会抛移异常,有兴趣的小伙伴可以实际验证一下)

配置绑定到List对象

在实际的应用场景中,配置为数组的可能性也很高,比如我有一个代理库,对应的相关配置如下

demo:
  proxy:
    - ip: 127.0.0.1
      port: 1080
    - ip: localhost
      port: 1800

此时我们的实际使用姿势可以如下

  • 首先定义Proxy类

  • @Data
    public class Proxy {
        private String ip;
        private Integer port;
    }

对应的手动绑定方式

// 将配置绑定到list
List<Proxy> proxyList = binder.bind("demo.proxy", Bindable.listOf(Proxy.class)).get();
// 或者直接使用 binder.bindOrCreate("demo.proxy", Bindable.listOf(Proxy.class))
System.out.println("list config: " + proxyList);

输出结果如下

配置绑定到Map对象

我们写一个简单的配置模拟上面的场景

demo:
  dynamic:
    master:
      user: main
      password: m1
    slave:
      user: slave
      password: s1

在上面的配置中,master/slave 为数据源名称,在下面的配置则为数据源配置信息,结构都一致;基于此,我们需要声明的配置类实际为

@Data
public class DsConfig {
    private String user;
    private String password;
}

配置绑定的实现也很简单,与上面List的类似

Map<String, DsConfig> dsMap = binder.bind("demo.dynamic", Bindable.mapOf(String.class, DsConfig.class)).get();
System.out.println("Map Config: " + dsMap);

执行之后的输出结果如下

Map Config: {master=BindHelper.DsConfig(user=main, password=m1), slave=BindHelper.DsConfig(user=slave, password=s1)}
 配置转换处理

上面介绍的姿势都是直接将配置绑定到对应的java对象上,那么我们是否会存在需要对配置属性进行特殊处理的场景呢?

这种场景当然也不算少见,如驼峰与下划线的互转,如密码之类的配置文件中属于加密填写,应用加载时需要解密之后使用等

对于这种场景,我们也给出一个简单的实例,在配置文件中,添加一个base64加密的数据

demo:
  enc:
    pwd: 5LiA54Gw54GwYmxvZw==

对应的解析方式

// 对配置进行解析
String decPwd = binder.bind("demo.enc.pwd", Bindable.of(String.class))
        .map(s -> new String(Base64Utils.decodeFromString(s))).get();
System.out.println("解码之后的数据是: " + decPwd);

执行之后,实际输出结果如下:

解码之后的数据是: Java技术前沿
绑定方法回调

除了上面介绍到的属性绑定姿势之外,Binder还非常贴心的给大家提供了过程回调,给你提供更灵活的控制方式

// 注册绑定过程回调
String dec = binder.bindOrCreate("demo.enc.pwd", Bindable.of(String.class), new BindHandler() {
    @Override
    public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
        System.out.println("开始绑定: " + name);
        return BindHandler.super.onStart(name, target, context);
    }
 
    @Override
    public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
        System.out.println("绑定成功!" + name + " val:" + target.getValue() + " res: " + result);
        return new String(Base64Utils.decodeFromString((String) result));
    }
 
    @Override
    public Object onCreate(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
        System.out.println("创建: " + name + " val:" + target.getValue() + " res: " + result);
        return BindHandler.super.onCreate(name, target, context, result);
    }
 
    @Override
    public Object onFailure(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Exception error) throws Exception {
        System.out.println("绑定失败! " + name + " " + error.getMessage());
        return BindHandler.super.onFailure(name, target, context, error);
    }
 
    @Override
    public void onFinish(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) throws Exception {
        System.out.println("绑定结束: " + name + " val:" + target.getValue() + " res: " + result);
        BindHandler.super.onFinish(name, target, context, result);
    }
});
System.out.println("绑定回调:" + dec);

同样是实现配置解密,如上面的方式也是可行的,对应的输出如

开始绑定: demo.enc.pwd
绑定成功!demo.enc.pwd val:null res: 5LiA54Gw54GwYmxvZw==
绑定结束: demo.enc.pwd val:null res: Java技术前沿绑定回调: Java技术前沿

小结

本文的知识点比较简单,属于看过就会的范畴,但是它的实际应用场景可以说非常多;特别是当我们在某些场景下,直接使用SpringBoot的属性配置绑定不太好实现时,如动态数据源、配置的回调处理等,不妨考虑借助Binder来实现编程式的配置绑定加载

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值