SpringBoot中极大的简化了项目中对于属性配置的加载方式,可以简单的通过 @Value
, @ConfigurationProperties
来实现属性配置与Java POJO对象、Bean的成员变量的绑定,那如果出现一个某些场景,需要我们手动的、通过编程式的方式,将属性配置与给定的pojo对象进行绑定,我们又应该怎么实现呢?
I. 项目配置 1. 依赖 首先搭建一个标准的SpringBoot项目工程,相关版本以及依赖如下
本项目借助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
进行开发
2. 启动入口 我们使用默认的配置进行测试,因此启动入口也可以使用最基础的
1 2 3 4 5 6 @SpringBootApplication public class Application { public static void main (String[] args) { SpringApplication.run(Application.class ) ; } }
II. 实例演示 1. 基础知识点Binder 本文的目的主要是给大家介绍编程式的属性绑定,当然除了我们最熟悉的直接写代码,从Environment
中获取配置之外,还可以使用Binder来更方便的实现属性绑定
因此我们首先来了解一下这个不太常出现在CURD的日常工作中的Binder类:
Binder JavaDoc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static Binder get (Environment environment) { return get(environment, (BindHandler)null ); } <T> BindResult<T> bind (String name, Class<T> target) <T> BindResult<T> bind (String name, Bindable<T> target, BindHandler handler) <T> T bindOrCreate (String name, Class<T> target) <T> T bindOrCreate (String name, Bindable<T> target, BindHandler handler)
两种常见的使用姿势:
bind方法: 将属性绑定到对应的类上, 不会返回null
bindOrCreate: 将属性绑定到对应的类上, 返回结果可能为null
2. 实例演示 接下来我们看几个常见的使用姿势
2.1 配置绑定到POJO属性类 直接将配置绑定到我们自定义的属性配置类上,也就是我们最常见的、可直接利用@ConfigurationProperties
来实现的使用方式
我们在配置文件中,添加一个基础的配置
1 2 3 4 5 6 7 demo: mail: host: smtp.163.com from: xhhuiblog@163.com username: test password: testpwd port: 465
接下来定义一个对应的属性配置类Mail
1 2 3 4 5 6 7 8 @Data public class Mail { private String host; private String port; private String user; private String password; private String from; }
然后我们的使用姿势,将如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @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对于不存在时,如何表现
微调一下上面的bindInfo()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 方法调用不会抛移异常,有兴趣的小伙伴可以实际验证一下)
2.2 配置绑定到List对象 在实际的应用场景中,配置为数组的可能性也很高,比如我有一个代理库,对应的相关配置如下
1 2 3 4 5 6 demo: proxy: - ip: 127.0 .0 .1 port: 1080 - ip: localhost port: 1800
此时我们的实际使用姿势可以如下
1 2 3 4 5 @Data public class Proxy { private String ip; private Integer port; }
对应的手动绑定方式
1 2 3 4 List<Proxy> proxyList = binder.bind("demo.proxy" , Bindable.listOf(Proxy.class )).get () ; System.out.println("list config: " + proxyList);
输出结果如下
1 list config: [BindHelper.Proxy(ip=127.0.0.1, port=1080), BindHelper.Proxy(ip=localhost, port=1800)]
2.3 配置绑定到Map对象 将属性配置绑定到一个Map的场景也不算少见,如之前写过的多数据源自主切换的实现方式中,就有这么个场景
我们写一个简单的配置模拟上面的场景
1 2 3 4 5 6 7 8 demo: dynamic: master: user: main password: m1 slave: user: slave password: s1
在上面的配置中,master/slave 为数据源名称,在下面的配置则为数据源配置信息,结构都一致;基于此,我们需要声明的配置类实际为
1 2 3 4 5 @Data public class DsConfig { private String user; private String password; }
配置绑定的实现也很简单,与上面List的类似
1 2 Map<String, DsConfig> dsMap = binder.bind("demo.dynamic" , Bindable.mapOf(String.class , DsConfig .class )).get () ; System.out.println("Map Config: " + dsMap);
执行之后的输出结果如下
1 Map Config: {master=BindHelper.DsConfig(user=main, password=m1), slave=BindHelper.DsConfig(user=slave, password=s1)}
2.4 配置转换处理 上面介绍的姿势都是直接将配置绑定到对应的java对象上,那么我们是否会存在需要对配置属性进行特殊处理的场景呢?
这种场景当然也不算少见,如驼峰与下划线的互转,如密码之类的配置文件中属于加密填写,应用加载时需要解密之后使用等
对于这种场景,我们也给出一个简单的实例,在配置文件中,添加一个base64加密的数据
1 2 3 demo: enc: pwd: 5LiA54Gw54GwYmxvZw==
对应的解析方式
1 2 3 4 String decPwd = binder.bind("demo.enc.pwd" , Bindable.of(String.class )) .map (s -> new String (Base64Utils .decodeFromString (s ))).get () ;System.out.println("解码之后的数据是: " + decPwd);
执行之后,实际输出结果如下:
2.5 绑定方法回调 除了上面介绍到的属性绑定姿势之外,Binder还非常贴心的给大家提供了过程回调,给你提供更灵活的控制方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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);
同样是实现配置解密,如上面的方式也是可行的,对应的输出如
1 2 3 4 开始绑定: demo.enc.pwd 绑定成功!demo.enc.pwd val:null res: 5LiA54Gw54GwYmxvZw== 绑定结束: demo.enc.pwd val:null res: 一灰灰blog 绑定回调: 一灰灰blog
3. 小结 本文的知识点比较简单,属于看过就会的范畴,但是它的实际应用场景可以说非常多;特别是当我们在某些场景下,直接使用SpringBoot的属性配置绑定不太好实现时,如动态数据源、配置的回调处理等,不妨考虑借助Binder来实现编程式的配置绑定加载
其次本文只介绍了Binder类的使用姿势,有好气的小伙伴,自然会想了解它的具体实现姿势,它是怎么实现配置属性与java实体类进行绑定的呢? 类型转换如何支持的呢? 如果让我们自己来实现配置绑定,可以怎么支持呢?
不妨再进一步,让我们实现一个自定义的配置加载、解析、绑定并注入到Spring容器的解决方案,可以怎么整?
III. 不能错过的源码和相关知识点 0. 项目
1. 微信公众号: 一灰灰Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
一灰灰blog
Be the first person to leave a comment!