欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot中動態(tài)更新@Value配置方式

 更新時間:2023年09月18日 08:40:46   作者:曾小二的禿頭之路  
這篇文章主要介紹了SpringBoot中動態(tài)更新@Value配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

SpringBoot動態(tài)更新@Value配置

1 背景

通常我們在項目運行過程中,會有修改配置的需求,但是在沒有接入分布式配置中心的情況下,經常修改一個配置就需要重啟一次容器,但是項目的重啟時間久,而且重啟還會影響用戶的使用,因此需要在不重啟的情況下,動態(tài)修改配置。

我們可以通過以下兩種方式,實現 @Value 配置的動態(tài)更新。

2 通過反射實現@Value配置的更新

2.1 代碼實現

首先,我們需要創(chuàng)建一個對象,來保存 @Value 的所有信息。

@Getter
public class SpringValue {
    /**
     * bean 的弱引用
     */
    private final WeakReference<Object> beanRef;
    /**
     * bean 名稱
     */
    private final String beanName;
    /**
     * 字段
     */
    private final Field field;
    /**
     * 屬性的鍵
     */
    private final String key;
    /**
     * 對應的占位符
     */
    private final String placeholder;
    /**
     * 字段的類對象
     */
    private final Class<?> targetType;
    public SpringValue(String key, String placeholder, Object bean, String beanName, Field field) {
        this.beanRef = new WeakReference<>(bean);
        this.beanName = beanName;
        this.field = field;
        this.key = key;
        this.placeholder = placeholder;
        this.targetType = field.getType();
    }
    @SneakyThrows
    public void update(Object newVal) {
        injectField(newVal);
    }
    /**
     * 使用反射,給字段注入新的值
     *
     * @param newVal 新的值
     * @throws IllegalAccessException 發(fā)送反射異常時
     */
    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);
    }
}

然后我們需要個注冊表,來保存 key 和 SpringValue 的映射關系。

因為一個 key 有可能對應多個 SpringValue,所以這里使用 Multimap。

public class SpringValueRegistry {
    private static final SpringValueRegistry INSTANCE = new SpringValueRegistry();
    private final Multimap<String, SpringValue> registry = LinkedListMultimap.create();
    private final Object lock = new Object();
    private SpringValueRegistry() {
    }
    public static SpringValueRegistry getInstance() {
        return INSTANCE;
    }
    public void register(String key, SpringValue springValue) {
        synchronized (lock) {
            registry.put(key, springValue);
        }
    }
    public Collection<SpringValue> get(String key) {
        return registry.get(key);
    }
    public void updateValue(String key, Object newValue) {
        get(key).forEach(springValue -> springValue.update(newValue));
    }
}

當然,我們還需要一個工具類,來解析占位符。

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 = "}";
    private static final PlaceholderHelper INSTANCE = new PlaceholderHelper();
    private PlaceholderHelper() {
    }
    public static PlaceholderHelper getInstance() {
        return INSTANCE;
    }
    /**
     * 解析占位符
     *
     * @param propertyString 占位符字符串
     * @return 獲取鍵
     */
    public Set<String> extractPlaceholderKeys(String propertyString) {
        Set<String> placeholderKeys = new HashSet<>();
        if (!StringUtils.hasText(propertyString) ||
                (!isNormalizedPlaceholder(propertyString) &&
                        !isExpressionWithPlaceholder(propertyString))) {
            return placeholderKeys;
        }
        Deque<String> stack = new LinkedList<>();
        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) {
                // 找不到占位符
                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 (StringUtils.hasText(defaultValuePart)) {
                        stack.push(defaultValuePart);
                    }
                }
            }
            // 有剩余部分,例如: ${a}.$
            if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
                String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
                if (StringUtils.hasText(remainingPart)) {
                    stack.push(remainingPart);
                }
            }
        }
        return placeholderKeys;
    }
    /**
     * 判斷是不是標準的占位符,即以 '${' 開頭,并且包含 '}'
     *
     * @param propertyString 屬性字符串
     * @return 如果是標準的占位符,則返回 true
     */
    private boolean isNormalizedPlaceholder(String propertyString) {
        return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
    }
    private boolean isExpressionWithPlaceholder(String propertyString) {
        return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.contains(EXPRESSION_SUFFIX)
                && propertyString.contains(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
    }
    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;
    }
    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());
    }
}

接著,我們就可以依賴于 spring boot 的生命周期,繼承 BeanPostProcessor,來處理 @Value 注解的值,將其注冊到注冊表中。

@Slf4j
@Component
public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered {
    private final SpringValueRegistry springValueRegistry;
    private final PlaceholderHelper placeholderHelper;
    public SpringValueProcessor() {
        this.springValueRegistry = SpringValueRegistry.getInstance();
        this.placeholderHelper = PlaceholderHelper.getInstance();
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = bean.getClass();
        for (Field field : findAllField(clazz)) {
            processField(bean, beanName, field);
        }
        return bean;
    }
    private void processField(Object bean, String beanName, Field field) {
        // 查找有 @Value 注釋的字段
        Value value = field.getAnnotation(Value.class);
        if (value == null) {
            return;
        }
        doRegister(bean, beanName, field, value);
    }
    private void doRegister(Object bean, String beanName, Field field, Value value) {
        Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
        if (keys.isEmpty()) {
            return;
        }
        for (String key : keys) {
            SpringValue springValue;
            springValue = new SpringValue(key, value.value(), bean, beanName, field);
            springValueRegistry.register(key, springValue);
            log.info("Monitoring {}", springValue);
        }
    }
    @Override
    public int getOrder() {
        // 設置為最低優(yōu)先級
        return Ordered.LOWEST_PRECEDENCE;
    }
    private List<Field> findAllField(Class<?> clazz) {
        final List<Field> res = new LinkedList<>();
        ReflectionUtils.doWithFields(clazz, res::add);
        return res;
    }
}

至此,我們就已經實現了 @Value 注解動態(tài)更新的主要邏輯了,我們通過一個測試用例來看一下效果。

2.2 測試用例

我們在 resources 目錄下,創(chuàng)建一個配置文件 application-dynamic.properties 。

zzn.dynamic.name=default-name

然后新建配置文件對應的配置類

@Component
@Getter
@PropertySource(value={"classpath:application-dynamic.properties"})
public class DynamicProperties {
    @Value("${zzn.dynamic.name}")
    private String dynamicName;
}

測試方法如下:

@SpringBootTest
class SpringValueApplicationTests {
    @Autowired
    private DynamicProperties dynamicProperties;
    @Test
    void testDynamicUpdateValue() {
        Assertions.assertEquals("default-name", dynamicProperties.getDynamicName());
        SpringValueRegistry.getInstance().updateValue("zzn.dynamic.name", "dynamic-name");
        Assertions.assertEquals("dynamic-name", dynamicProperties.getDynamicName());
    }
}

3 通過Scope實現@Value配置的更新

3.1 代碼實現

首先,我們可以繼承 Scope 接口,實現我們自定義的 Scope。

@Slf4j
public class BeanRefreshScope implements Scope {
    public static final String SCOPE_REFRESH = "refresh";
    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();
    /**
     * 使用 Map 緩存 bean 實例
     */
    private final ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>();
    private BeanRefreshScope() {
    }
    public static BeanRefreshScope getInstance() {
        return INSTANCE;
    }
    /**
     * 清理 bean 緩存
     */
    public static void clear() {
        INSTANCE.beanMap.clear();
    }
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        log.info("BeanRefreshScope, get bean name: {}", name);
        return beanMap.computeIfAbsent(name, s -> objectFactory.getObject());
    }
    @Override
    public Object remove(String name) {
        return beanMap.remove(name);
    }
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }
    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }
    @Override
    public String getConversationId() {
        return null;
    }
}

然后,將我們自定義的 scope 注入到 spring context 中。

@Configuration
public class ScopeConfig {
    @Bean
    public CustomScopeConfigurer customScopeConfigurer() {
        CustomScopeConfigurer customScopeConfigurer = new CustomScopeConfigurer();
        Map<String, Object> map = new HashMap<>();
        map.put(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
        // 配置 scope
        customScopeConfigurer.setScopes(map);
        return customScopeConfigurer;
    }
}

定義一個注解,方便我們快速使用

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope(BeanRefreshScope.SCOPE_REFRESH)
@Documented
public @interface RefreshScope {
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

定義一個工具類,實現配置的替換。

@Component
public class RefreshConfigUtil implements EnvironmentAware {
    private static ConfigurableEnvironment environment;
    public static void updateValue(String key, Object newValue) {
        // 自定義的配置文件名稱
        MutablePropertySources propertySources = environment.getPropertySources();
        propertySources.stream()
                .forEach(x -> {
                    if (x instanceof MapPropertySource) {
                        MapPropertySource propertySource = (MapPropertySource) x;
                        if (propertySource.containsProperty(key)) {
                            String name = propertySource.getName();
                            Map<String, Object> source = propertySource.getSource();
                            Map<String, Object> map = new HashMap<>(source.size());
                            map.putAll(source);
                            map.put(key, newValue);
                            environment.getPropertySources().replace(name, new MapPropertySource(name, map));
                        }
                    }
                });
        // 刷新緩存
        BeanRefreshScope.clear();
    }
    @Override
    public void setEnvironment(Environment environment) {
        RefreshConfigUtil.environment = (ConfigurableEnvironment) environment;
    }
}

接下來,我們使用測試用例來驗證一下。

3.2 測試用例

我們同上一個用例一樣,在 resources 目錄下,創(chuàng)建一個配置文件 application-dynamic.properties 。

zzn.dynamic.name=default-name

然后新建配置文件對應的配置類,區(qū)別在于這個配置文件上面加了 @RefreshScope 注解

@RefreshScope
@Component
@Getter
@PropertySource(value={"classpath:application-dynamic.properties"})
public class DynamicProperties {
    @Value("${zzn.dynamic.name}")
    private String dynamicName;
}

測試方法如下:

@SpringBootTest
class SpringValueApplicationTests {
    @Autowired
    private DynamicProperties dynamicProperties;
    @Test
    void testDynamicUpdateValue() {
        Assertions.assertEquals("default-name", dynamicProperties.getDynamicName());
        RefreshConfigUtil.updateValue("zzn.dynamic.name", "dynamic-name");
        Assertions.assertEquals("dynamic-name", dynamicProperties.getDynamicName());
    }
}

通過測試用例,可以看到,也是可以實現我們動態(tài)替換配置的功能。

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • IntelliJ IDEA遠程Debug Linux的Java程序,找問題不要只會看日志了(推薦)

    IntelliJ IDEA遠程Debug Linux的Java程序,找問題不要只會看日志了(推薦)

    這篇文章主要介紹了IntelliJ IDEA遠程Debug Linux的Java程序,找問題不要只會看日志了,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • Java的List集合框架之LinkedList詳細解析

    Java的List集合框架之LinkedList詳細解析

    這篇文章主要介紹了Java的List集合框架之LinkedList詳細解析,LinkedList底層是內部Node類的存儲,prev、next、item值,同時最外層還有first、last節(jié)點,需要的朋友可以參考下
    2023-11-11
  • SpringBoot3集成swagger文檔的使用方法

    SpringBoot3集成swagger文檔的使用方法

    本文介紹了Swagger的誕生背景、主要功能以及如何在Spring Boot 3中集成Swagger文檔,Swagger可以幫助自動生成API文檔,實現與代碼同步,并提供交互式測試功能,使用方法包括導入依賴、配置文檔、使用常見注解以及訪問生成的Swagger UI和文檔,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • 如何自定義springboot-starter日志組件供各個服務使用(系統(tǒng)日志優(yōu)化)

    如何自定義springboot-starter日志組件供各個服務使用(系統(tǒng)日志優(yōu)化)

    文章介紹了如何將各個微服務的接口調用日志邏輯優(yōu)化為一個可共享的Spring Boot Starter,通過自定義注解和自動裝配機制實現,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • IDEA配置java開發(fā)環(huán)境(maven、gradle、tomcat)

    IDEA配置java開發(fā)環(huán)境(maven、gradle、tomcat)

    這篇文章主要介紹了IDEA配置java開發(fā)環(huán)境(maven、gradle、tomcat),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • springboot application.properties 文件注入數組方式

    springboot application.properties 文件注入數組方式

    這篇文章主要介紹了springboot application.properties 文件注入數組方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • IntelliJ?IDEA教程之clean或者install?Maven項目的操作方法

    IntelliJ?IDEA教程之clean或者install?Maven項目的操作方法

    這篇文章主要介紹了IntelliJ?IDEA教程之clean或者install?Maven項目的操作方法,本文分步驟給大家介紹兩種方式講解如何調試出窗口,需要的朋友可以參考下
    2023-04-04
  • Java 根據某個 key 加鎖的實現方式

    Java 根據某個 key 加鎖的實現方式

    日常開發(fā)中,有時候需要根據某個 key 加鎖,確保多線程情況下,對該 key 的加鎖和解鎖之間的代碼串行執(zhí)行,這篇文章主要介紹了Java 根據某個 key 加鎖的實現方式,需要的朋友可以參考下
    2023-03-03
  • SpringBoot+slf4j實現全鏈路調用日志跟蹤的方法(一)

    SpringBoot+slf4j實現全鏈路調用日志跟蹤的方法(一)

    本文重點給大家介紹Tracer集成的slf4j MDC功能,方便用戶在只簡單修改日志配置文件的前提下輸出當前 Tracer 上下文 TraceId,文章通過代碼給大家講解了在springboot中使用的技巧,感興趣的朋友跟隨小編一起看看吧
    2021-05-05
  • java ThreadPoolExecutor 并發(fā)調用實例詳解

    java ThreadPoolExecutor 并發(fā)調用實例詳解

    這篇文章主要介紹了java ThreadPoolExecutor 并發(fā)調用實例詳解的相關資料,需要的朋友可以參考下
    2017-05-05

最新評論