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 and Jwt 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

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

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, 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

一灰灰blog


小灰灰Blog
251 声望46 粉丝