SpringBoot basic articles @Value which knowledge points you don't know
Seeing this title, it’s a bit exaggerated. @Value
? Isn’t it just a binding configuration? Is there any special gameplay?
(If you have mastered the problems listed below, there is really no need to look down)
@Value
does not exist, what will happen?- How to set the default value
- Can the list in the configuration file be directly mapped to the list attribute?
- Three configuration methods for mapping configuration parameters to simple objects
- In addition to configuration injection, do you understand the literal and SpEL support?
- Is remote (such as db, configuration center, http) configuration injection feasible?
<!-- more -->
Next, due to space limitations, the first few questions raised above will be explained, and the last two will be placed in the next part.
I. Project environment
First create a SpringBoot project for testing, the source code is posted at the end, and the friendly prompts are more friendly to read the source code
1. Project dependencies
This project is developed with the help of SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
2. Configuration file
In the configuration file, add some configuration information for testing
application.yml
auth:
jwt:
token: TOKEN.123
expire: 1622616886456
whiteList: 4,5,6
blackList:
- 100
- 200
- 300
tt: token:tt_token; expire:1622616888888
II. Use case
1. Basic posture
To introduce configuration parameters through ${}
, of course, the premise is that the class is managed by Spring, which is what we often call bean
As follows, a common use posture
@Component
public class ConfigProperties {
@Value("${auth.jwt.token}")
private String token;
@Value("${auth.jwt.expire}")
private Long expire;
}
2. The configuration does not exist, throw an exception
Next, introduce an injection that does not exist in the configuration. When the project is started, an exception will be thrown, causing it to fail to start normally
/**
* 不存在,使用默认值
*/
@Value("${auth.jwt.no")
private String no;
The exception thrown belongs to BeanCreationException
, and the corresponding exception prompt Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'auth.jwt.no' in value "${auth.jwt.no}"
So in order to avoid the above problems, generally speaking, it is recommended to set a default value, the rule is like ${key:default value}, the right side of the semicolon is the default value, when there is no related configuration, use the default value to initialize
/**
* 不存在,使用默认值
*/
@Value("${auth.jwt.no}")
private String no;
3. List configuration
In the whiteList in the configuration file, the corresponding value is 4,5,6
, separated by commas. For parameter values in this format, you can directly assign List<Long>
/**
* 英文逗号分隔,转列表
*/
@Value("${auth.jwt.whiteList}")
private List<Long> whiteList;
The above one belongs to the correct posture, but the following one does not work
/**
* yml数组,无法转换过来,只能根据 "auth.jwt.blackList[0]", "auth.jwt.blackList[1]" 来取对应的值
*/
@Value("${auth.jwt.blackList:10,11,12}")
private String[] blackList;
Although our configuration parameter auth.jwt.blackList
is an array, it cannot be mapped to the blackList above (even if it is changed to List<String>
, it will not work, not because it is declared as String[]
)
We can see how the configuration is by looking at Evnrionment
By auth.jwt.blackList
not get configuration information, only through auth.jwt.blackList[0]
, auth.jwt.blackList[1]
to get
So the problem is here, how to solve this?
To solve the problem, the key is to know @Value
, here is the key class org.springframework.context.support.PropertySourcesPlaceholderConfigurer
The key point is in the place circled above. When we find this place, we can start the game. A more trivial method, as follows
// 使用自定义的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);
}
}
description:
- The above implementation posture is very inelegant. There should be a more concise way to make sense. Please advise me if you know.
4. Configure to entity class
Usually, @Value
only modifies the basic type. If I want to convert the configuration to an entity class, is it possible?
Of course it is feasible, and there are three support positions
PropertyEditor
Converter
Formatter
Next, convert auth.jwt.tt
configured above
auth:
jwt:
tt: token:tt_token; expire:1622616888888
Map to Jwt object
@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
Please note that PropertyEditor
is an interface defined in the java bean specification, which is mainly used to edit bean properties. Spring provides support; we hope to convert String to bean property type. Generally speaking, it is a POJO corresponding to an Editor.
So customize a JwtEditor
public class JwtEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(Jwt.parse(text, "JwtEditor"));
}
}
Next, you need to register the 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;
}
}
Description
- When the above
JwtEditor
andJwt
objects are under the same package path, the above active registration is not required, and Spring will automatically register (it is so intimate)
After the above configuration is completed, it can be injected correctly
/**
* 借助 PropertyEditor 来实现字符串转对象
*/
@Value("${auth.jwt.tt}")
private Jwt tt;
4.2 Converter
Spring's Converter interface is also relatively common, at least more used than the above one, and the posture is relatively simple, implement the interface and then register.
public class JwtConverter implements Converter<String, Jwt> {
@Override
public Jwt convert(String s) {
return Jwt.parse(s, "JwtConverter");
}
}
Register conversion class
/**
* 注册自定义的converter
*
* @return
*/
@Bean("conversionService")
public ConversionServiceFactoryBean conversionService() {
ConversionServiceFactoryBean factoryBean = new ConversionServiceFactoryBean();
factoryBean.setConverters(Collections.singleton(new JwtConverter()));
return factoryBean;
}
Test again, the same can be injected successfully
4.3 Formatter
Finally, I will introduce the use of Formatter, which is more common in localization-related operations
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);
}
}
Register as well (please note that when we use the registered Formatter, we need to comment out the registered bean of the previous Converter)
@Bean("conversionService")
public FormattingConversionServiceFactoryBean conversionService2() {
FormattingConversionServiceFactoryBean factoryBean = new FormattingConversionServiceFactoryBean();
factoryBean.setConverters(Collections.singleton(new JwtConverter()));
factoryBean.setFormatters(Collections.singleton(new JwtFormatter()));
return factoryBean;
}
When Converter and Formatter exist at the same time, the latter has higher priority
5. Summary
Due to space limitations, I will stop here for the time being. In view of the issues mentioned above, I will make a simple summary.
@Value
the configuration declared by 060c4385d25fec does not exist, an exception is thrown (the project will not start)- The above problem can be solved by setting the default value (syntax
${xxx:defaultValue})
yaml
configuration cannot be directly bound to the list/array@Value
- The configuration value is a comma-separated scene, which can be directly assigned to a list/array
Does not support the direct conversion of the value in the configuration file to a non-simple object, there are three ways if necessary
- Use
PropertyEditor
achieve type conversion - Use
Converter
achieve type conversion (this method is more recommended) - Use
Formater
achieve type conversion
- Use
In addition to the above knowledge points, give answers to the questions raised at the beginning
@Value
supports literals and SpEL expressions- Since SpEL expressions are supported, of course the remote configuration injection we need can be achieved
Now that you have seen this, let me ask two more questions. In SpringCloud microservices, if you use SpringCloud Config, you can also @Value
. So what is the principle?
@Value
binding configuration, if you want to achieve dynamic refresh, is it feasible? How to play if you can?
(If you don’t mind, follow the WeChat public account "Yihuihuiblog", and the answer will be given in the next blog post)
III. Source code and related knowledge points not to be missed
0. Project
- Engineering: https://github.com/liuyueyi/spring-boot-demo
- Source code: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/002-properties-value
series of blog posts, with better reading effect
- [Basic Series] Implement a custom configuration loader (application)
- [Basic Series] The default configuration of SpringBoot configuration information
- [Basic Series] Configuration refresh of SpringBoot configuration information
- [Basic series] SpringBoot basics configuration information custom configuration designation and configuration reference
- [Basic series] SpringBoot basics configuration information and many environment configuration information
- [Basic Series] How to read the configuration information of SpringBoot basics
1. A Grey Blog
It is not as good as the letter. The above content is purely a family statement. Due to limited personal ability, it is inevitable that there will be omissions and errors. If you find a bug or have better suggestions, you are welcome to criticize and correct me, and I am grateful.
The following is a gray personal blog, which records all the blog posts in study and work. Welcome everyone to visit
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。