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

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

Seeing this title, it's a bit exaggerated. @ValueWho 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)

  • @ValueThe 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 BeanCreationExceptionthe 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.blackListis 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.blackListYou 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 @Valuethe 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, @Valueonly 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 PropertyEditorit 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 JwtEditorand Jwtobject 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.

  • @ValueWhen 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 (syntax
  • yamlArrays in configuration, cannot be @Valuebound 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 PropertyEditorimplementation type conversion
    • Use Converterimplementation type conversion (more recommended)
    • Use Formaterimplementation type conversion

In addition to the above knowledge points, give answers to the questions asked at the beginning

  • @ValueLiterals 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 @Valueinject remote configuration through it. So what is this principle?

@ValueBinding 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

A series of blog posts, with better reading effect

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 grey blog

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324029847&siteId=291194637