SpringBoot根底篇@Value中哪些你不晓得的知识点

看到这个题目,有点夸大了啊,@Value 这个谁不晓得啊,不就是绑定配置么,还能有什么非凡的玩法不成?

(如果上面列出的这些问题,曾经熟练掌握,那的确没啥往下面看的必要了)

  • @Value对应的配置不存在,会怎么?
  • 默认值如何设置
  • 配置文件中的列表能够间接映射到列表属性上么?
  • 配置参数映射为简略对象的三种配置形式
  • 除了配置注入,字面量、SpEL反对是否理解?
  • 近程(如db,配置核心,http)配置注入可行否?

<!-- more -->

接下来,限于篇幅问题,将针对下面提出的问题的后面几条进行阐明,最初两个放在下篇

I. 我的项目环境

先创立一个用于测试的SpringBoot我的项目,源码在最初贴出,情谊提醒源码浏览更敌对

1. 我的项目依赖

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

2. 配置文件

在配置文件中,加一些用于测试的配置信息

application.yml

auth:  jwt:    token: TOKEN.123    expire: 1622616886456    whiteList: 4,5,6    blackList:      - 100      - 200      - 300    tt: token:tt_token; expire:1622616888888

II. 应用case

1. 根本姿态

通过${}来引入配置参数,当然前提是所在的类被Spring托管,也就是咱们常说的bean

如下,一个常见的应用姿态

@Componentpublic class ConfigProperties {    @Value("${auth.jwt.token}")    private String token;    @Value("${auth.jwt.expire}")    private Long expire;}

2. 配置不存在,抛异样

接下来,引入一个配置不存在的注入,在我的项目启动的时候,会发现抛出异样,导致无奈失常启动

/** * 不存在,应用默认值 */@Value("${auth.jwt.no")private String no;

抛出的异样属于BeanCreationException, 对应的异样提醒 Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'auth.jwt.no' in value "${auth.jwt.no}"

所以为了防止下面的问题,一般来讲,倡议设置一个默认值,规定如 ${key:默认值}, 在分号左边的就是默认值,当没有相干配置时,应用默认值初始化

/** * 不存在,应用默认值 */@Value("${auth.jwt.no}")private String no;

3. 列表配置

在配置文件中whiteList,对应的value是 4,5,6, 用英文逗号分隔,对于这种格局的参数值,能够间接赋予List<Long>

/** * 英文逗号分隔,转列表 */@Value("${auth.jwt.whiteList}")private List<Long> whiteList;

下面这个属于正确的应用姿态,然而上面这个却不行了

/** * yml数组,无奈转换过去,只能依据 "auth.jwt.blackList[0]", "auth.jwt.blackList[1]" 来取对应的值 */@Value("${auth.jwt.blackList:10,11,12}")private String[] blackList;

尽管咱们的配置参数 auth.jwt.blackList是数组,然而就没法映射到下面的blackList (即便换成 List<String> 也是不行的,并不是因为申明为String[]的起因)

咱们能够通过查看Evnrionment来看一下配置是怎么的

通过auth.jwt.blackList是拿不到配置信息的,只能通过auth.jwt.blackList[0], auth.jwt.blackList[1]来获取

那么问题来了,怎么解决这个呢?

要解决问题,要害就是须要晓得@Value的工作原理,这里间接给出要害类 org.springframework.context.support.PropertySourcesPlaceholderConfigurer

关键点就在下面圈出的中央,找到这里,咱们就能够入手开撸,一个比拟猥琐的办法,如下

// 应用自定义的bean代替Spring的@Primary@Componentpublic class MyPropertySourcesPlaceHolderConfigure extends PropertySourcesPlaceholderConfigurer {    @Autowired    protected Environment environment;    /**     * {@code PropertySources} from the given {@link Environment}     * will be searched when replacing ${...} placeholders.     *     * @see #setPropertySources     * @see #postProcessBeanFactory     */    @Override    public void setEnvironment(Environment environment) {        super.setEnvironment(environment);        this.environment = environment;    }    @SneakyThrows    @Override    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, ConfigurablePropertyResolver propertyResolver) throws BeansException {        // 实现一个拓展的PropertySource,反对获取数组格局的配置信息        Field field = propertyResolver.getClass().getDeclaredField("propertySources");        boolean access = field.isAccessible();        field.setAccessible(true);        MutablePropertySources propertySource = (MutablePropertySources) field.get(propertyResolver);        field.setAccessible(access);        PropertySource source = new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {            @Override            @Nullable            public String getProperty(String key) {                // 对数组进行兼容                String ans = this.source.getProperty(key);                if (ans != null) {                    return ans;                }                StringBuilder builder = new StringBuilder();                String prefix = key.contains(":") ? key.substring(key.indexOf(":")) : key;                int i = 0;                while (true) {                    String subKey = prefix + "[" + i + "]";                    ans = this.source.getProperty(subKey);                    if (ans == null) {                        return i == 0 ? null : builder.toString();                    }                    if (i > 0) {                        builder.append(",");                    }                    builder.append(ans);                    ++i;                }            }        };        propertySource.addLast(source);        super.processProperties(beanFactoryToProcess, propertyResolver);    }}

阐明:

  • 下面这种实现姿态很不优雅,讲道理应该有更简洁的形式,有请晓得的老哥指教一二

4. 配置转实体类

通常,@Value只润饰根本类型,如果我想将配置转换为实体类,可性否?

当然是可行的,而且还有三种反对姿态

  • PropertyEditor
  • Converter
  • Formatter

接下来针对下面配置的auth.jwt.tt进行转换

auth:  jwt:    tt: token:tt_token; expire:1622616888888

映射为Jwt对象

@Datapublic class Jwt {    private String source;    private String token;    private Long expire;        // 实现string转jwt的逻辑    public static Jwt parse(String text, String source) {        String[] kvs = StringUtils.split(text, ";");        Map<String, String> map = new HashMap<>(8);        for (String kv : kvs) {            String[] items = StringUtils.split(kv, ":");            if (items.length != 2) {                continue;            }            map.put(items[0].trim().toLowerCase(), items[1].trim());        }        Jwt jwt = new Jwt();        jwt.setSource(source);        jwt.setToken(map.get("token"));        jwt.setExpire(Long.valueOf(map.getOrDefault("expire", "0")));        return jwt;    }}

4.1 PropertyEditor

请留神PropertyEditor是java bean标准中的,次要用于对bean的属性进行编辑而定义的接口,Spring提供了反对;咱们心愿将String转换为bean属性类型,一般来讲就是一个POJO,对应一个Editor

所以自定义一个 JwtEditor

public class JwtEditor extends PropertyEditorSupport {    @Override    public void setAsText(String text) throws IllegalArgumentException {        setValue(Jwt.parse(text, "JwtEditor"));    }}

接下来就须要注册这个Editor

@Configurationpublic class AutoConfiguration {    /**     * 注册自定义的 propertyEditor     *     * @return     */    @Bean    public CustomEditorConfigurer editorConfigurer() {        CustomEditorConfigurer editorConfigurer = new CustomEditorConfigurer();        editorConfigurer.setCustomEditors(Collections.singletonMap(Jwt.class, JwtEditor.class));        return editorConfigurer;    }}

阐明

  • 当下面的JwtEditorJwt对象,在雷同的包门路上面的时候,不须要下面的被动注册,Spring会主动注册 (就是这么贴心)

下面这个配置结束之后,就能够正确的被注入了

/** * 借助 PropertyEditor 来实现字符串转对象 */@Value("${auth.jwt.tt}")private Jwt tt;

4.2 Converter

Spring的Converter接口也比拟常见,至多比下面这个用得多一些,应用姿态也比较简单,实现接口、而后注册即可

public class JwtConverter implements Converter<String, Jwt> {    @Override    public Jwt convert(String s) {        return Jwt.parse(s, "JwtConverter");    }}

注册转换类

/** * 注册自定义的converter * * @return */@Bean("conversionService")public ConversionServiceFactoryBean conversionService() {    ConversionServiceFactoryBean factoryBean = new ConversionServiceFactoryBean();    factoryBean.setConverters(Collections.singleton(new JwtConverter()));    return factoryBean;}

再次测试,同样能够注入胜利

4.3 Formatter

最初再介绍一个Formatter的应用姿态,它更常见于本地化相干的操作

public class JwtFormatter implements Formatter<Jwt> {    @Override    public Jwt parse(String text, Locale locale) throws ParseException {        return Jwt.parse(text, "JwtFormatter");    }    @Override    public String print(Jwt object, Locale locale) {        return JSONObject.toJSONString(object);    }}

同样注册一下(请留神,咱们应用注册Formatter时,须要将后面Converter的注册bean给正文掉)

@Bean("conversionService")public FormattingConversionServiceFactoryBean conversionService2() {    FormattingConversionServiceFactoryBean factoryBean = new FormattingConversionServiceFactoryBean();    factoryBean.setConverters(Collections.singleton(new JwtConverter()));    factoryBean.setFormatters(Collections.singleton(new JwtFormatter()));    return factoryBean;}

当Converter与Formatter同时存在时,后者优先级更高

5. 小结

限于篇幅,这里就暂告一段落,针对后面提到的几个问题,做一个简略的演绎小结

  • @Value 申明的配置不存在时,抛异样(我的项目会起不来)
  • 通过设置默认值(语法 ${xxx:defaultValue})能够解决下面的问题
  • yaml配置中的数组,无奈间接通过@Value绑定到列表/数组上
  • 配置值为英文逗号分隔的场景,能够间接赋值给列表/数组
  • 不反对将配置文件中的值间接转换为非简略对象,如果有须要有三种形式

    • 应用PropertyEditor实现类型转换
    • 应用Converter实现类型转换 (更举荐应用这种形式)
    • 应用Formater实现类型转换

除了下面的知识点之外,针对最开始提出的问题,给出答案

  • @Value反对字面量,也反对SpEL表达式
  • 既然反对SpEL表达式,当然就能够实现咱们需要的近程配置注入了

既然曾经看到这里了,那么就再提两个问题吧,在SpringCloud微服务中,如果应用了SpringCloud Config,也是能够通过@Value来注入近程配置的,那么这个原理又是怎么的呢?

@Value绑定的配置,如果想实现动静刷新,可行么?如果能够怎么玩?

(棘手不介意的话,关注下微信公众号"一灰灰blog", 下篇博文就给出答案)

III. 不能错过的源码和相干知识点

0. 我的项目

  • 工程:https://github.com/liuyueyi/spring-boot-demo
  • 源码: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/002-properties-value

系列博文,配合浏览成果更好哦

  • 【根底系列】实现一个自定义配置加载器(利用篇)
  • 【根底系列】SpringBoot配置信息之默认配置
  • 【根底系列】SpringBoot配置信息之配置刷新
  • 【根底系列】SpringBoot根底篇配置信息之自定义配置指定与配置内援用
  • 【根底系列】SpringBoot根底篇配置信息之多环境配置信息
  • 【根底系列】SpringBoot根底篇配置信息之如何读取配置信息

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因集体能力无限,不免有疏漏和谬误之处,如发现bug或者有更好的倡议,欢送批评指正,不吝感谢

上面一灰灰的集体博客,记录所有学习和工作中的博文,欢送大家前去逛逛

  • 一灰灰Blog集体博客 https://blog.hhui.top
  • 一灰灰Blog-Spring专题博客 http://spring.hhui.top