SpringBoot2.動(dòng)態(tài)@Value的實(shí)現(xiàn)方式
title: SpringBoot2.動(dòng)態(tài)@Value實(shí)現(xiàn)
前言
前面文章有詳細(xì)描述過各個(gè)不同階段對(duì)于bean的擴(kuò)展接口
所以今天就基于BeanPostProcessor實(shí)現(xiàn)Spring中的@Value注解值動(dòng)態(tài)變化
基于上面也可以實(shí)現(xiàn)一個(gè)配置中心,比如說Apollo
具體的實(shí)現(xiàn)步驟分為如下幾步
1.通過BeanPostProcessor取得有使用@Value注解的bean,并存儲(chǔ)到map中
2.動(dòng)態(tài)修改map中的bean字段的值
獲取bean
首先寫一個(gè)類實(shí)現(xiàn)BeanPostProcessor接口,只需要使用其中的一個(gè)函數(shù)就可以。前后都可以用來實(shí)現(xiàn),并不影響最終的使用,因?yàn)樵蹅冎皇切枰猙ean的實(shí)例。
接下來看一下具體實(shí)現(xiàn)代碼
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<Field> fields = findAllField(obj);
for (Field field : fields) {
Value value = field.getAnnotation(Value.class);
if (value != null) {
Set<String> 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<Field> findAllField(Class clazz) {
final List<Field> res = new LinkedList<>();
ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
res.add(field);
}
});
return res;
}
}
上面的代碼咱們就已經(jīng)拿到了SpringValueController這個(gè)實(shí)例bean并存儲(chǔ)到了map當(dāng)中,下面看一下測試代碼
/**
* cache field,存儲(chǔ)bean 字段
*/
package com.allen.apollo;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
public class SpringValueCacheMap {
public static final Multimap<String, SpringValue> 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<Object> 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.
* <br />
* <br />
* "${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.
* <ul>
* <li>${some.key} => "some.key"</li>
* <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>
* <li>${${some.key}} => "some.key"</li>
* <li>${${some.key:other.key}} => "some.key"</li>
* <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>
* <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>
* </ul>
*/
public Set<String> extractPlaceholderKeys(String propertyString) {
Set<String> placeholderKeys = Sets.newHashSet();
if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) {
return placeholderKeys;
}
Stack<String> 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}.$
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);
}
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合atomikos實(shí)現(xiàn)跨庫事務(wù)的詳細(xì)方案
這篇文章主要介紹了SpringBoot整合atomikos實(shí)現(xiàn)跨庫事務(wù),業(yè)務(wù)主要涉及政府及企業(yè)且并發(fā)量不大,所以采用XA事務(wù),雖然性能有所損失,但是可以保證數(shù)據(jù)的強(qiáng)一致性,需要的朋友可以參考下2022-06-06
Java?Stream函數(shù)式編程管道流結(jié)果處理
這篇文章主要為大家介紹了Java?Stream函數(shù)式編程管道流結(jié)果處理的示例過程解析需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
Mybatis之動(dòng)態(tài)sql標(biāo)簽的使用
這篇文章主要介紹了Mybatis之動(dòng)態(tài)sql標(biāo)簽的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Spring Boot 訪問安全之認(rèn)證和鑒權(quán)詳解
這篇文章主要介紹了Spring Boot 訪問安全之認(rèn)證和鑒權(quán),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
MyBatis的@SelectProvider注解構(gòu)建動(dòng)態(tài)SQL方式
這篇文章主要介紹了MyBatis的@SelectProvider注解構(gòu)建動(dòng)態(tài)SQL方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Mybatis游標(biāo)查詢大量數(shù)據(jù)方式
這篇文章主要介紹了Mybatis游標(biāo)查詢大量數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
SpringBoot?@InitBinder注解綁定請(qǐng)求參數(shù)的過程詳解
這篇文章主要介紹了SpringBoot?@InitBinder注解綁定請(qǐng)求參數(shù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04

