What knowledge points you don't know in SpringBoot basics @Value
Seeing this title, it's a bit exaggerated. @Value
Who doesn't know this, isn't it just binding configuration, what other special gameplay can't be done?
(If you have mastered the problems listed below, there is really no need to look down)
@Value
The corresponding configuration does not exist, what happens?- How to set the default value
- Can lists in configuration files be directly mapped to list properties?
- Three configuration methods for mapping configuration parameters to simple objects
- In addition to configuration injection, do you understand literals and SpEL support?
- Is remote (such as db, configuration center, http) configuration injection feasible?
<!-- more -->
Next, due to space limitations, I will explain the first few of the questions raised above, and the last two will be placed in the next article.
I. Project Environment
First create a SpringBoot project for testing, the source code is posted at the end, friendly reminder source code reading is more friendly
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
Introduce configuration parameters through ${}
, of course, the premise is that the class in which it is located is managed by Spring, which is what we often call beans
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 with a configuration that does not exist. When the project starts, it will find that an exception is thrown, causing it to fail to start normally.
/**
* 不存在,使用默认值
*/
@Value("${auth.jwt.no")
private String no;
The thrown exception belongs to BeanCreationException
the corresponding exception promptCaused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'auth.jwt.no' in value "${auth.jwt.no}"
Therefore, in order to avoid the above problems, generally speaking, it is recommended to set a default value. For example ${key:默认值}
, the one to the right 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 configuration file whiteList, the corresponding value is 4,5,6
, separated by English commas. For parameter values in this format, you can directly assign them toList<Long>
/**
* 英文逗号分隔,转列表
*/
@Value("${auth.jwt.whiteList}")
private List<Long> whiteList;
The above is the correct posture, but the following is not
/**
* 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 replaced List<String>
with , it is not because of String[]
the reason declared as)
We can see what the configuration looks like by looking at Evnrionment
auth.jwt.blackList
You ca n't get the configuration information through the pass, you can only get it through auth.jwt.blackList[0]
,auth.jwt.blackList[1]
So the question is, how to solve this?
To solve the problem, the key is to know @Value
the working principle. Here are the key classes.org.springframework.context.support.PropertySourcesPlaceholderConfigurer
The key point is in the place circled above, and when we find it, we can start it. A more obscene method is 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);
}
}
illustrate:
- The above implementation posture is very inelegant. There should be a more concise way to reason. If you know, please give me some advice.
4. Configure the transfer entity class
Usually, @Value
only basic types are decorated, if I want to convert configuration to entity class, is it possible?
Of course it works, and there are three support poses
PropertyEditor
Converter
Formatter
Next auth.jwt.tt
, convert the above configuration
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
it is an interface defined in the java bean specification, which is mainly used for editing bean properties. Spring provides support; we want to convert String to bean property type, which is generally a POJO, corresponding to an Editor
So customize aJwtEditor
public class JwtEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(Jwt.parse(text, "JwtEditor"));
}
}
Next, you need to register this 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;
}
}
illustrate
- When the above
JwtEditor
andJwt
object are under the same package path, the active registration above is not required, and Spring will automatically register (that's how 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 it is used more than the above one, and the use posture is relatively simple, just 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 injection can also be successful
4.3 Formatter
Finally, introduce a Formatter usage posture, 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);
}
}
Also register it (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 a while, and make a brief summary of the issues mentioned above.
@Value
When the declared configuration does not exist, an exception is thrown (the project will not start)${xxx:defaultValue})
The above problem can be solved by setting a default value (syntaxyaml
Arrays in configuration, cannot be@Value
bound directly to lists/arrays- The configuration value is a comma-separated scene, which can be directly assigned to a list/array
- It is not supported to directly convert the value in the configuration file to a non-simple object, if necessary, there are three ways
- Use
PropertyEditor
implementation type conversion - Use
Converter
implementation type conversion (more recommended) - Use
Formater
implementation type conversion
- Use
In addition to the above knowledge points, give answers to the questions asked at the beginning
@Value
Literals are supported, as well as SpEL expressions- Since SpEL expressions are supported, of course, the remote configuration injection we need can be implemented
Now that you have seen this, let's ask two more questions. In SpringCloud microservices, if you use SpringCloud Config, you can also @Value
inject remote configuration through it. So what is this principle?
@Value
Binding configuration, if you want to achieve dynamic refresh, is it feasible? How to play if possible?
(If you don’t mind, pay attention to the WeChat public account "Yashhui blog", and the answer will be given in the next blog post)
III. Source code and related knowledge points that cannot 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
A series of blog posts, with better reading effect
- [Basic Series] Implementing a Custom Configuration Loader (Application)
- [Basic series] Default configuration of SpringBoot configuration information
- [Basic series] Configuration refresh of SpringBoot configuration information
- [Basic series] Custom configuration specification and configuration reference for SpringBoot basic configuration information
- [Basic Series] SpringBoot Basics Configuration Information Multiple Environment Configuration Information
- [Basic Series] How to Read Configuration Information in Spring Boot Basics
1. A gray blog
It is not as good as a letter. The above content is purely from the family. Due to limited personal ability, it is inevitable that there will be omissions and mistakes. If you find bugs or have better suggestions, you are welcome to criticize and correct them.
The following is a gray personal blog, recording all blog posts in study and work, welcome everyone to visit
- A Hui Hui Blog Personal blog https://blog.hhui.top
- A gray blog-Spring special blog http://spring.hhui.top