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
如下,一个常见的应用姿态
@Component
public 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
@Component
public 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 对象
@Data
public 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
@Configuration
public class AutoConfiguration {
/**
* 注册自定义的 propertyEditor
*
* @return
*/
@Bean
public CustomEditorConfigurer editorConfigurer() {CustomEditorConfigurer editorConfigurer = new CustomEditorConfigurer();
editorConfigurer.setCustomEditors(Collections.singletonMap(Jwt.class, JwtEditor.class));
return editorConfigurer;
}
}
阐明
- 当下面的
JwtEditor
与Jwt
对象,在雷同的包门路上面的时候,不须要下面的被动注册,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