What knowledge points you don't know in @Value of SpringBoot Basics

What knowledge points you don't know in @Value of SpringBoot Basics

What are the 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 a binding configuration? Is there any special gameplay that can't be achieved?

(If you have mastered the problems listed below, there is really no need to look down)

  • @Value
    The corresponding configuration 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?
  • 3.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?

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 uses

SpringBoot 2.2.1.RELEASE
+
maven 3.5.3
+
IDEA
Develop

2. Configuration file

In the configuration file, add some configuration information for testing

application.yml

the auth: JWT: token: TOKEN.123 The expire: 1622616886456 WhiteList: . 4 , 5,6 BLACKLIST: - 100 - 200 is - 300 TT: token: tt_token; The expire: 1622616888888 copy the code

II. Use case

1. Basic posture

by

${}
To introduce configuration parameters, 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; } Copy code

2. The configuration does not exist, throw an exception

Next, introduce an injection that does not exist in the configuration. When the project starts, it will be found that an exception is thrown, causing it to fail to start normally

/** * Does not exist, use the default value */ @Value("${auth.jwt.no") private String no; Copy code

The exception thrown belongs to

BeanCreationException
, 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 rules such as

${key:default value}
, The one on the right side of the semicolon is the default value. When there is no related configuration, the default value is used to initialize

/** * Does not exist, use the default value */ @Value("${auth.jwt.no}") private String no; Copy code

3. List configuration

In the configuration file whiteList, the corresponding value is

4,5,6
, Separated by commas, for parameter values in this format, you can directly assign
List<Long>

/** * English comma separated, to list */ @Value("${auth.jwt.whiteList}") private List<Long> whiteList; Copy code

The above one belongs to the correct posture, but the following one does not work

/** * The yml array cannot be converted, it can only be taken according to "auth.jwt.blackList[0]", "auth.jwt.blackList[1]" */ @Value("${auth.jwt.blackList:10,11,12}") private String[] blackList; Copy code

Although our configuration parameters

auth.jwt.blackList
Is an array, but it cannot be mapped to the blackList above (even if it is replaced by
List<String>
Is also not working, not because it is declared as
String[]
s reason)

We can see how the configuration is by looking at Evnrionment

by

auth.jwt.blackList
The configuration information is not available, only through
auth.jwt.blackList[0]
,
auth.jwt.blackList[1]
To get

So the problem is coming, how to solve this?

To solve the problem, the key is to know

@Value
The working principle of the key category is directly given here
org.springframework.context.support.PropertySourcesPlaceholderConfigurer

The key point is in the place circled above. When we find it, we can start the game. A more awkward method is as follows

//Use a custom bean to replace Spring's @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 { //implement an expansion of PropertySource, support to get the array format configuration information 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) { //Compatibility with array 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); } } Copy code

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 modify the basic types, if I want to convert the configuration to an entity class, is it feasible?

Of course it is feasible, and there are three support positions

  • PropertyEditor
  • Converter
  • Formatter

Next for the above configuration

auth.jwt.tt
Make the conversion

the auth: JWT: TT: token: tt_token; The expire: 1622616888888 copy the code

Map to Jwt object

@Data public class Jwt { private String source; private String token; private Long expire; //Realize the logic of string to 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; } } Copy code

4.1 PropertyEditor

Please note

PropertyEditor
It is an interface defined in the java bean specification and 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 one

JwtEditor

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

Next, you need to register the Editor

@Configuration public class AutoConfiguration { /** * Register a custom propertyEditor * * @return */ @Bean public CustomEditorConfigurer editorConfigurer () { CustomEditorConfigurer editorConfigurer = new CustomEditorConfigurer(); editorConfigurer.setCustomEditors(Collections.singletonMap(Jwt.class, JwtEditor.class)); return editorConfigurer; } } Copy code

Description

  • When the above
    JwtEditor
    versus
    Jwt
    When the object is 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

/** * Realize string to object with the help of PropertyEditor */ @Value("${auth.jwt.tt}") private Jwt tt; Copy code

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" ); } } Copy code

Register conversion class

/** * Register a custom converter * * @return */ @Bean("conversionService") public ConversionServiceFactoryBean conversionService () { ConversionServiceFactoryBean factoryBean = new ConversionServiceFactoryBean(); factoryBean.setConverters(Collections.singleton( new JwtConverter())); return factoryBean; } Copy code

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); } } Copy code

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; } Copy code

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
    When the declared configuration does not exist, throw an exception (the project will not start)
  • By setting default values (syntax
    ${xxx:defaultValue})
    Can solve the above problem
  • yaml
    The array in the configuration cannot be passed directly
    @Value
    Bind to list/array
  • 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
      Implement type conversion
    • use
      Converter
      Realize type conversion (this method is more recommended)
    • use
      Formater
      Implement type conversion

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 pass

@Value
To inject remote configuration, so what is the principle?

@Value
The 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 "Yihuihui blog", and the answer will be given in the next blog post)

III. Source code and related knowledge points not to be missed

0. Project

A series of blog posts, with better reading effect

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, omissions and errors are inevitable. If you find a bug or have better suggestions, you are welcome to criticize and correct.

The following is a gray personal blog, which records all the blog posts in study and work. Welcome everyone to visit