最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

1 背景

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

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

2 通過反射實現(xiàn)@Value配置的更新

2.1 代碼實現(xiàn)

首先,我們需要創(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;
    /**
     * 對應(yīng)的占位符
     */
    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 的映射關(guān)系。

因為一個 key 有可能對應(yīng)多個 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));
    }
}

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

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;
    }
    /**
     * 判斷是不是標(biāo)準(zhǔn)的占位符,即以 '${' 開頭,并且包含 '}'
     *
     * @param propertyString 屬性字符串
     * @return 如果是標(biāo)準(zhǔn)的占位符,則返回 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() {
        // 設(shè)置為最低優(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;
    }
}

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

2.2 測試用例

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

zzn.dynamic.name=default-name

然后新建配置文件對應(yīng)的配置類

@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實現(xiàn)@Value配置的更新

3.1 代碼實現(xiàn)

首先,我們可以繼承 Scope 接口,實現(xiàn)我們自定義的 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;
}

定義一個工具類,實現(xiàn)配置的替換。

@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

然后新建配置文件對應(yīng)的配置類,區(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());
    }
}

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

總結(jié)

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

相關(guān)文章

  • 詳解RestTemplate?用法

    詳解RestTemplate?用法

    RestTemplate?是從?Spring3.0?開始支持的一個?HTTP?請求工具,也有的稱之為網(wǎng)絡(luò)框架,說白了就是Java版本的一個postman,這篇文章主要介紹了詳解RestTemplate?用法,需要的朋友可以參考下
    2022-07-07
  • springmvc處理模型數(shù)據(jù)ModelAndView過程詳解

    springmvc處理模型數(shù)據(jù)ModelAndView過程詳解

    這篇文章主要介紹了springmvc處理模型數(shù)據(jù)ModelAndView過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01
  • Spring?Boot自動配置源碼實例解析

    Spring?Boot自動配置源碼實例解析

    Spring Boot作為Java領(lǐng)域最為流行的快速開發(fā)框架之一,其核心特性之一就是其強大的自動配置機制,下面這篇文章主要給大家介紹了關(guān)于Spring?Boot自動配置源碼的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-08-08
  • Java基礎(chǔ)教程之String深度分析

    Java基礎(chǔ)教程之String深度分析

    這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)教程之String的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • springboot整合shiro多驗證登錄功能的實現(xiàn)(賬號密碼登錄和使用手機驗證碼登錄)

    springboot整合shiro多驗證登錄功能的實現(xiàn)(賬號密碼登錄和使用手機驗證碼登錄)

    這篇文章給大家介紹springboot整合shiro多驗證登錄功能的實現(xiàn)方法,包括賬號密碼登錄和使用手機驗證碼登錄功能,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2021-07-07
  • Springboot2.x+Quartz分布式集群的實現(xiàn)

    Springboot2.x+Quartz分布式集群的實現(xiàn)

    這篇文章主要介紹了Springboot2.x+Quartz分布式集群的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Java實現(xiàn)簡單的銀行管理系統(tǒng)的示例代碼

    Java實現(xiàn)簡單的銀行管理系統(tǒng)的示例代碼

    這篇文章主要介紹了如何利用Java實現(xiàn)簡單的銀行管理系統(tǒng),可以實現(xiàn)存款,取款,查詢等功能,文中的示例代碼講解詳細(xì),感興趣的可以了解一下
    2022-09-09
  • Java cglib為實體類(javabean)動態(tài)添加屬性方式

    Java cglib為實體類(javabean)動態(tài)添加屬性方式

    這篇文章主要介紹了Java cglib為實體類(javabean)動態(tài)添加屬性方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • jvm垃圾回收之GC調(diào)優(yōu)工具分析詳解

    jvm垃圾回收之GC調(diào)優(yōu)工具分析詳解

    這篇文章主要為大家介紹了jvm垃圾回收之GC調(diào)優(yōu)工具的分析詳解,在進行JVM?GC性能調(diào)優(yōu)之前,需要使用某些工具獲取到當(dāng)前應(yīng)用的狀態(tài)信息
    2022-01-01
  • java之路徑分隔符介紹

    java之路徑分隔符介紹

    考慮到程序的可移植性,創(chuàng)建文件時建議大家選用"/",因為經(jīng)過測試用java創(chuàng)建文件時在windows平臺下用“/”也是可以的,java貌似在后臺作過處理了。
    2013-03-03

最新評論

申扎县| 额尔古纳市| 金湖县| 黄陵县| 东兰县| 宁德市| 轮台县| 平塘县| 新郑市| 防城港市| 错那县| 巴中市| 平顶山市| 永丰县| 马公市| 临洮县| 佛山市| 文安县| 松阳县| 前郭尔| 海宁市| 双城市| 沁水县| 阿城市| 南汇区| 普陀区| 泾源县| 上林县| 长丰县| 历史| 兴安县| 启东市| 福建省| 蚌埠市| 韩城市| 曲阜市| 木兰县| 松阳县| 中牟县| 颍上县| 巢湖市|