只是SpringBoot并没有那么做,而是提供了一个@ConstructorBinding注解,让我们使用构造器绑定数据。
复杂配置 JavBean @Configuration @ConfigurationProperties(prefix="app") public class AppConfig{ private Map<String, DataSourceMetadata> multiDataSourceMap; public void setMultiDataSourceMap(Map<String, DataSourceMetadata> multiDataSourceMap){ this.multiDataSourceMap = multiDataSourceMap; } } public class DataSourceMetadata{ private String url; private String driverClass; private String username; private String passowrd; // 省略Setter和Getter } 配置文件 app: multiDataSourceMap: ds1: url: jdbc:// driver-class: com.mysql.cj.Driver username: xxx password: xxx ds2: url: jdbc:// driver-class: com.mysql.cj.Driver username: 12sds password: adfwqw # or app: multiDataSourceMap: ds1: { url: jdbc:// driver-class: com.mysql.cj.Driver username: xxx password: xxx } ds2: { url: jdbc:// driver-class: com.mysql.cj.Driver username: 12sds password: adfwqw }然后启动,走起,立马会发现熟悉又可气的NPE
原因很简单,SpringBoot没能从配置文件读取相应的配置数据并且实例化一个Map,因为
它现在面对的情况比以前复杂了,现在的JavaBean是一个Map的value值
解决方法就是使用构造器绑定的方式,并且需要在构造器使用此注解@ConstructorBinding
public class DataSourceMetadata{ private String url; private String driverClass; private String username; private String passowrd; @ConstructorBinding public DataSourceMetadata(String url, String driverClass, String username, String passowrd){ this.url = url; this.driverClass = driverClass; this.username = username; this.password = password; } // 省略Setter和Getter }只要这么一搞就正常了,不会有烦人的NPE
我并不知道是否有别的方式也可以做到,比如继续使用Setter方法来进行数据绑定
瘦身计划上面的这些配置,如果都有的话,全部写到application.yml或者application.properties文件中,会导致配置文件内容太多,而且各种配置混合在一起,不便于管理和维护。
如果需要改动某个配置,就要改动整个文件,存在一定的风险导致其他配置被误改。
所以应该一类配置,放到一起去管理。
同样的,一类配置通常对应一个功能,如果其中一项配置的改动,那么相应的测试,也能保证同一个配置文件的修改不会引发其他问题。
所以有必要将application.yml拆分了。
花了一番力气,拆分了一个出来upload.yml,然后使用如下方式引入配置文件
配置文件默认是放在 resources目录下(maven/gradle),配置文件在编译打包后,会位于classes的根目录下,也就是我们所谓的classpath
@Configuration @PropertySource("classpath:upload.yml") @ConfigurationProperties(prefix="upload") public class UploadConfig{ private String rootPath; private String fileType; private int fileSize; private boolean rename; // 省略 Setter方法 }问题来了,死活没法将数据绑定到JavaBean的属性上。
Debug看源码,陷进去出不来。试着使用profile来解决,虽然可以解决,但是毕竟不是同一个使用场景,并不合适。
最后找人求救,告知@PropertySource不支持yaml文件,仅支持properties,于是试了下,果然是的
SpringBoot版本是2.2.6,有个群友说他1.5的还是支持的,不过SpringBoot官方明确写到不支持的
2.7.4. YAML ShortcomingsYAML files cannot be loaded by using the @PropertySource annotation. So, in the case that you need to load values that way, you need to use a properties file.
上面看到,其实yaml配置更有优势一些,所以如果想继续使用yaml的话,也不是不可以
@PropertySource支持自定义文件格式 // 这里继承了DefaultPropertySourceFactory,也可以直接实现PropertySourceFactory public class YamlPropertySourceFactory extends DefaultPropertySourceFactory { public YamlPropertySourceFactory () { super(); } @Override public PropertySource<?> createPropertySource (String name, EncodedResource resource) throws IOException { // 这个判断是有必要的,因为直接使用name是null,没深究原因 String nameToUse = name != null ? name : resource.getResource().getFilename(); // yml文件,使用YamlPropertiesFactoryBean来从yaml文件Resource中构建一个Properties对象 // 然后使用PropertiesPropertySource封装成PropertySource对象,就能加入到Environment if (nameToUse.endsWith(".yml")) { YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean(); factoryBean.setResources(resource.getResource()); factoryBean.afterPropertiesSet(); return new PropertiesPropertySource(nameToUse, factoryBean.getObject()); } // 如果不是yml配置文件,使用默认实现 return super.createPropertySource(name, resource); } }使用时,@PropertySource(factory=YamlPropertySourceFactory.class)即可。
使用@Value@Value是Spring Framework的注解,不属于SpringBoot,其典型使用场景就是注入外部化配置属性,官方文档介绍
@Value使用Spring内建的转化器SimpleTypeConverter,这个支持Integer,String,和逗号分割的字符串数组。