Home  >  Article  >  Java  >  How to implement SpringBoot2 dynamic @Value

How to implement SpringBoot2 dynamic @Value

WBOY
WBOYforward
2023-05-11 17:40:19915browse

The specific implementation steps are divided into the following steps

1. Obtain the beans annotated with @Value through BeanPostProcessor and store them in the map

2. Dynamically modify the beans in the map The value of the field

Get the bean

First write a class to implement the BeanPostProcessor interface, and only need to use one of its functions. It can be implemented both before and after, and it does not affect the final use, because we only need instances of beans.

Let’s take a look at the specific implementation code

package com.allen.apollo;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@Configuration
public class SpringValueProcessor implements BeanPostProcessor {
    private final PlaceholderHelper placeholderHelper = new PlaceholderHelper();
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("springValueController")) {
            Class obj = bean.getClass();
            List fields = findAllField(obj);
            for (Field field : fields) {
                Value value = field.getAnnotation(Value.class);
                if (value != null) {
                    Set keys = placeholderHelper.extractPlaceholderKeys(value.value());
                    for (String key : keys) {
                        SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
                        SpringValueCacheMap.map.put(key, springValue);
                    }
                }
            }
        }
        return bean;
    }
    private List findAllField(Class clazz) {
        final List res = new LinkedList<>();
        ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                res.add(field);
            }
        });
        return res;
    }
}

In the above code, we have already obtained the instance bean of SpringValueController and stored it in the map. Let’s take a look at the test code

  /**
   * cache  field,存储bean 字段
   */
package com.allen.apollo;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
public class SpringValueCacheMap {
    public static final Multimap map = LinkedListMultimap.create();
}
 package com.allen.apollo;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import org.springframework.core.MethodParameter;
public class SpringValue {
    private MethodParameter methodParameter;
    private Field field;
    private WeakReference beanRef;
    private String beanName;
    private String key;
    private String placeholder;
    private Class targetType;
    private Type genericType;
    private boolean isJson;
    public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) {
        this.beanRef = new WeakReference<>(bean);
        this.beanName = beanName;
        this.field = field;
        this.key = key;
        this.placeholder = placeholder;
        this.targetType = field.getType();
        this.isJson = isJson;
        if (isJson) {
            this.genericType = field.getGenericType();
        }
    }
    public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) {
        this.beanRef = new WeakReference<>(bean);
        this.beanName = beanName;
        this.methodParameter = new MethodParameter(method, 0);
        this.key = key;
        this.placeholder = placeholder;
        Class[] paramTps = method.getParameterTypes();
        this.targetType = paramTps[0];
        this.isJson = isJson;
        if (isJson) {
            this.genericType = method.getGenericParameterTypes()[0];
        }
    }
    public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
        if (isField()) {
            injectField(newVal);
        } else {
            injectMethod(newVal);
        }
    }
    private void injectField(Object newVal) throws IllegalAccessException {
        Object bean = beanRef.get();
        if (bean == null) {
            return;
        }
        boolean accessible = field.isAccessible();
        field.setAccessible(true);
        field.set(bean, newVal);
        field.setAccessible(accessible);
    }
    private void injectMethod(Object newVal)
            throws InvocationTargetException, IllegalAccessException {
        Object bean = beanRef.get();
        if (bean == null) {
            return;
        }
        methodParameter.getMethod().invoke(bean, newVal);
    }
    public String getBeanName() {
        return beanName;
    }
    public Class getTargetType() {
        return targetType;
    }
    public String getPlaceholder() {
        return this.placeholder;
    }
    public MethodParameter getMethodParameter() {
        return methodParameter;
    }
    public boolean isField() {
        return this.field != null;
    }
    public Field getField() {
        return field;
    }
    public Type getGenericType() {
        return genericType;
    }
    public boolean isJson() {
        return isJson;
    }
    boolean isTargetBeanValid() {
        return beanRef.get() != null;
    }
    @Override
    public String toString() {
        Object bean = beanRef.get();
        if (bean == null) {
            return "";
        }
        if (isField()) {
            return String
                    .format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass().getName(), field.getName());
        }
        return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(),
                methodParameter.getMethod().getName());
    }
}
package com.allen.apollo;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.util.StringUtils;
import java.util.Set;
import java.util.Stack;
/**
 * Placeholder helper functions.
 */
public class PlaceholderHelper {
  private static final String PLACEHOLDER_PREFIX = "${";
  private static final String PLACEHOLDER_SUFFIX = "}";
  private static final String VALUE_SEPARATOR = ":";
  private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
  private static final String EXPRESSION_PREFIX = "#{";
  private static final String EXPRESSION_SUFFIX = "}";
  /**
   * Resolve placeholder property values, e.g.
   * 
*
* "${somePropertyValue}" -> "the actual property value" */ public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) { // resolve string value String strVal = beanFactory.resolveEmbeddedValue(placeholder); BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory .getMergedBeanDefinition(beanName) : null); // resolve expressions like "#{systemProperties.myProp}" return evaluateBeanDefinitionString(beanFactory, strVal, bd); } private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value, BeanDefinition beanDefinition) { if (beanFactory.getBeanExpressionResolver() == null) { return value; } Scope scope = (beanDefinition != null ? beanFactory .getRegisteredScope(beanDefinition.getScope()) : null); return beanFactory.getBeanExpressionResolver() .evaluate(value, new BeanExpressionContext(beanFactory, scope)); } /** * Extract keys from placeholder, e.g. *
    *
  • ${some.key} => "some.key"
  • *
  • ${some.key:${some.other.key:100}} => "some.key", "some.other.key"
  • *
  • ${${some.key}} => "some.key"
  • *
  • ${${some.key:other.key}} => "some.key"
  • *
  • ${${some.key}:${another.key}} => "some.key", "another.key"
  • *
  • #{new java.text.SimpleDateFormat("${some.key}").parse("${another.key}")} => "some.key", "another.key"
  • *
*/ public Set extractPlaceholderKeys(String propertyString) { Set placeholderKeys = Sets.newHashSet(); if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) { return placeholderKeys; } Stack stack = new Stack<>(); stack.push(propertyString); while (!stack.isEmpty()) { String strVal = stack.pop(); int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); if (startIndex == -1) { placeholderKeys.add(strVal); continue; } int endIndex = findPlaceholderEndIndex(strVal, startIndex); if (endIndex == -1) { // invalid placeholder? continue; } String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); // ${some.key:other.key} if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) { stack.push(placeholderCandidate); } else { // some.key:${some.other.key:100} int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR); if (separatorIndex == -1) { stack.push(placeholderCandidate); } else { stack.push(placeholderCandidate.substring(0, separatorIndex)); String defaultValuePart = normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length())); if (!Strings.isNullOrEmpty(defaultValuePart)) { stack.push(defaultValuePart); } } } // has remaining part, e.g. ${a}.${b} if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) { String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length())); if (!Strings.isNullOrEmpty(remainingPart)) { stack.push(remainingPart); } } } return placeholderKeys; } private boolean isNormalizedPlaceholder(String propertyString) { return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.endsWith(PLACEHOLDER_SUFFIX); } private boolean isExpressionWithPlaceholder(String propertyString) { return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.endsWith(EXPRESSION_SUFFIX) && propertyString.contains(PLACEHOLDER_PREFIX); } private String normalizeToPlaceholder(String strVal) { int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); if (startIndex == -1) { return null; } int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX); if (endIndex == -1) { return null; } return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length()); } private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { int index = startIndex + PLACEHOLDER_PREFIX.length(); int withinNestedPlaceholder = 0; while (index < buf.length()) { if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) { if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + PLACEHOLDER_SUFFIX.length(); } else { return index; } } else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) { withinNestedPlaceholder++; index = index + SIMPLE_PLACEHOLDER_PREFIX.length(); } else { index++; } } return -1; } }
package com.allen.apollo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.InvocationTargetException;
@RestController
@Slf4j
public class SpringValueController {
    @Value("${test:123}")
    public String zax;
    @Value("${test:123}")
    public String test;
    @Value(("${zed:zed}"))
    public String zed;
    @GetMapping("/test")
    public String test(String a, String b) {
        if (!StringUtils.isEmpty(a)) {
            try {
                for (SpringValue springValue : SpringValueCacheMap.map.get("test")) {
                    springValue.update(a);
                }
                for (SpringValue springValue : SpringValueCacheMap.map.get("zed")) {
                    springValue.update(b);
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return String.format("test: %s, zax: %s, zed: %s", test, zax, zed);
    }
}

The above is the detailed content of How to implement SpringBoot2 dynamic @Value. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete