河北唐山:关于SpringBoot的外部化设置使用纪录

admin 5个月前 (05-17) 科技 42 0

关于SpringBoot的外部化设置使用纪录

声明: 若有任何纰漏、错误请不惜指出!

纪录下使用SpringBoot设置时遇到的一些贫苦,虽然这种贫苦是由于知识匮乏导致的。

纪录下制止一段时间后自己又给忘记了,以防万一。

若是放到博客里能辅助到遇到同样问题的同志,自是极好!

SpringBoot的外部化设置,主要就是指平时用到的一些设置文件,这些设置由于不是硬编码,放在了设置文件中,以是相对来说是一个外部化的设置Externalized Configuration

SpringBoot官方外部化设置的在线文档Externalized Configuration

低级用法

SpringBoot对设置提供了极大的便利,仅仅需要编写一个Yaml文件或者Properties文件,凭据其划定的花样,书写好我们的设置信息,然后编写一个响应的Java类,使用注解@ConfigurationProperties@Configuration配合使用,或者@Configuration@Value配合使用,即可将设置的值,映射到我们设置类或者JavaBean中。

有如下Java设置类

@Configuration
@ConfigurationProperties(prefix="spring.server")
public class AppConfig{
  private String name;
  private String port;
  public void setName(String name){
    this.name = name;
  }
  public void setPort(String port){
    this.port = port;
  }
}

如下设置文件,

Yaml花样设置文件

# application.yml
spring:
    server:
        name: spring-app
        port: 9527

Properties花样设置文件

# application.properties
spring.server.name=spring-app
spring.server.port=9237

使用@ConfigurationProperties,必须要有Setter方式,绑准时是通过Setter方式绑定的

参见org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization,这是一个BeanPostProcessor

这样在SpringBoot中,我们就可以将AppConfig这个Bean注入到其余Bean中使用我们的设置了。

以上这些在开发中基本上也就知足需要了,大部分我们的设置都很简朴,通常都是数值型的和字符串。

然则,凡事不能绝对。

高级用法

以下设置参考这位

Array/List

假如有如下需求,应用仅对几个有限的IP开放接见,然后我们想把这几个获得允许的IP地址写在设置文件中。

这个时刻若是设置剖析仅仅支持字符串和数值型的话,就需要我们自己获取到设置值以后,再去举行一些后续的处置,好比转换成数组或者列表。

幸亏,优异的框架,总能知足大部分的需求,SpringBoot是直接设置直接到数组或者列表的映射到

使用方式

Java设置类

@Configuration
@ConfigurationProperties(prefix="allows")
public class AllowedAccessConfig{
  private String[] ipList; // 字段类型可以是 List<String>
  public void setPort(String[] port){
    this.ipList = ipList;
  }
}

设置文件

# application.yml
allows:
      ipList: 
	  - 192.168.1.1
	  - 192.168.1.2
	  - 192.168.1.3
	  - 192.168.1.4
# or
allows:
     ipList: 192.168.1.1, 192.168.1.2, 192.168.1.3, 192.168.1.4
# application.properties
allows.ipList[0]=192.168.1.1
allows.ipList[1]=192.168.1.2
allows.ipList[2]=192.168.1.3
allows.ipList[3]=192.168.1.4
# or
allows.ipList= 192.168.1.1, 192.168.1.2, 192.168.1.3, 192.168.1.4

Map

若是数组或者列表不知足需求,需要key-vlaue型的,没问题,SpringBoot也是支持的。

假设一个对接方差别的营业,使用了差别的AES密钥,那么在设置的时刻,就要凭据营业类型作为key,对应的密钥作为value

Java设置类

@Configuration
@ConfigurationProperties(prefix="aes.keys")
public class AesKeyConfig{
  private Map<String,String> keys;
  public void setKeys(Map<String,String> keys){
    this.keys = keys;
  }
}

设置文件

# application.yml
aes:
    keys:
        order: 28jsaS2asf2fSA2
        pay: @ra@3safdsR5&sDa
# or
aes:
    keys[order]: 28jsaS2asf2fSA2
    keys[pay]: @ra@3safdsR5&sDa
# application.properties
aes.keys.order=28jsaS2asf2fSA2
aes.keys.pay=@ra@3safdsR5&sDa
# or
aes.keys[order]=28jsaS2asf2fSA2
aes.keys[pay]=@ra@3safdsR5&sDa

Enum

枚举?那必须支持

不外实际意义不怎么大,若是设置的值要可以转换成枚举值的话,设置的值必须和枚举值的name一致,大小写都不能差,由于SpringBoot实现的设置到枚举的转换,使用的是

/* java.lang.Enum#valueOf */
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                            String name) {
  // 这里的name就是枚举值的字符示意,一样平常都是大写的
  T result = enumType.enumConstantDirectory().get(name);
  if (result != null)
    return result;
  if (name == null)
    throw new NullPointerException("Name is null");
  throw new IllegalArgumentException(
    "No enum constant " + enumType.getCanonicalName() + "." + name);
}

关于这段代码的明白,可以参考另外一片文章深入明白Java枚举

若是枚举另有其他字段的话,就没办法了

JavaBean

什么? 照样不能知足?想要直接把设置绑定到一个JavaBean

干就完事了!

JavaBean

@Configuration
@ConfigurationProperties(prefix="upload")
public class UploadConfig{
  private String rootPath;
  private String fileType;
  private int fileSize;
	private boolean rename;
  // 省略 Setter方式
}

设置文件

# application.yml
upload:
	root-path: /xx/xx/xx
	file-type: zip
	fileSize: 1M
	rename: false
# application.properties
upload.rootPath=/xx/xx/xx
upload.fileType=zip
upload.fileSize=1M
upload.rename=false

以上几种用法,可以组合使用,异常的天真

不外若是是JavaBean的数组或者List,或者作为Mapvalue,会发现绑定不上去。

缘故原由在于,绑定默认是基于Setter方式,举行单个字段的绑定,赋值,而这里要的是一个JavaBean,需要建立一个JavaBean工具,再去做属性绑定赋值。

若是凭据这两步走,也可以做到乐成绑定到一个作为元素的JavaBean工具。

只是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是一个Mapvalue

解决方式就是使用组织器绑定的方式,而且需要在组织器使用此注解@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 Shortcomings

YAML 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

@ValueSpring Framework的注解,不属于SpringBoot,其典型使用场景就是注入外部化设置属性,官方文档@Values先容

@Value使用Spring内建的转化器SimpleTypeConverter,这个支持IntegerString,和逗号支解的字符串数组。

若是以为支持不够,照样可以自定义转换支持,自定义一个Converter,然后加入到ConverterService这个Bean中,由于后面的BeanPostProcessor依赖的就是ConverterService来处置转换的

以是若是有一些庞大的设置,最好照样使用SpringBoot的方式。

@Value的优势在于,它支持SpEL,而且可以使用在随便一个Bean的方式参数或者字段上

以是这是两种差别的使用场景,看情形自己选择。

不外总体小我私家倾向于前面一种,由于若是在其他的Bean中直接使用@Value,万一我们要改设置的名字了,效果由于使用了@Value,遍布的四处都是,改起来很贫苦,以是从治理维护的角度来说,@Value太野了。

顺便说一下对@Value的处置位置org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean,固然这里也是处置@Inject @Autowired @Resource的地方

后记

从设置文件到程序中使用到设置的值,一共履历两大步

  1. 读取设置内容到Environment中,无论任何形式的设置,最后都是一个Property Source
  2. 通过BeanPostProcessor来举行设置值绑定注入

若是不知足properties或者yaml花样的设置,可以自定义PropertySourceLoader,可以参考

org.springframework.boot.env.YamlPropertySourceLoaderorg.springframework.boot.env.PropertiesPropertySourceLoader

,

Sunbet

Sunbet申博 www.sunbet88.us是Sunbet指定的Sunbet官网,Sunbet提供Sunbet(Sunbet)、Sunbet、申博代理合作等业务。

AllBetGaming声明:该文看法仅代表作者自己,与本平台无关。转载请注明:河北唐山:关于SpringBoot的外部化设置使用纪录

网友评论

  • (*)

最新评论

文章归档

站点信息

  • 文章总数:794
  • 页面总数:0
  • 分类总数:8
  • 标签总数:1349
  • 评论总数:363
  • 浏览总数:32707