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

詳解spring項目中如何動態(tài)刷新bean

 更新時間:2023年08月29日 10:56:16   作者:linyb極客之路  
這篇文章主要為大家介紹了詳解spring項目中如何動態(tài)刷新bean,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

前陣子和朋友聊天,他手頭上有個spring單體項目,每次數(shù)據庫配置變更,他都要重啟項目,讓配置生效。他就想說有沒有什么辦法,不重啟項目,又可以讓配置生效。當時我就跟他說,可以用配置中心,他的意思是因為是維護類項目,不想再額外引入一個配置中心,增加運維成本。后邊跟他討論了一個方案,可以實現(xiàn)一個監(jiān)聽配置文件變化的程序,當監(jiān)聽到文件變化,進行相應的變更操作。具體流程如下

在這些步驟,比較麻煩就是如何動態(tài)刷新bean,因為朋友是spring項目,今天就來聊下在spring項目中如何實現(xiàn)bean的動態(tài)刷新

實現(xiàn)思路

了解spring的朋友,應該知道spring的單例bean是緩存在singletonObjects這個map里面,所以可以通過變更singletonObjects來實現(xiàn)bean的刷新。

我們可以通過調用removeSingleton和addSingleton這兩個方法來實現(xiàn),但是這種實現(xiàn)方式的缺點就是會改變bean的生命周期,會導致原來的一些增強功能失效,比如AOP。

但spring作為一個極其優(yōu)秀的框架,他提供了讓我們自己管理bean的擴展點。這個擴展點就是通過指定scope,來達到自己管理bean的效果

實現(xiàn)步驟

1、自定義scope

public class RefreshBeanScope implements Scope {
    private final Map<String,Object> beanMap = new ConcurrentHashMap<>(256);
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        if(beanMap.containsKey(name)){
            return beanMap.get(name);
        }
        Object bean = objectFactory.getObject();
        beanMap.put(name,bean);
        return bean;
    }
    @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;
    }
}

2、自定義scope注冊

public class RefreshBeanScopeDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    }
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            beanFactory.registerScope(SCOPE_NAME,new RefreshBeanScope());
    }
}

3、自定義scope注解(可選)

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refreshBean")
@Documented
public @interface RefreshBeanScope {



    /**
     * @see Scope#proxyMode()
     * @return proxy mode
     */
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

4、編寫自定義scope bean刷新邏輯

@RequiredArgsConstructor
public class RefreshBeanScopeHolder implements ApplicationContextAware {
    private final DefaultListableBeanFactory beanFactory;
    private ApplicationContext applicationContext;
    public List<String> refreshBean(){
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        List<String> refreshBeanDefinitionNames = new ArrayList<>();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            if(SCOPE_NAME.equals(beanDefinition.getScope())){
                beanFactory.destroyScopedBean(beanDefinitionName);
                beanFactory.getBean(beanDefinitionName);
                refreshBeanDefinitionNames.add(beanDefinitionName);
                applicationContext.publishEvent(new RefreshBeanEvent(beanDefinitionName));
            }
        }
        return Collections.unmodifiableList(refreshBeanDefinitionNames);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

以上步驟就是實現(xiàn)自定義scope管理bean的過程,下面我們以一個配置變更實現(xiàn)bean刷新例子,來演示以上步驟

示例

1、創(chuàng)建屬性配置文件

在項目src/main/rescoures目錄下創(chuàng)建屬性配置文件config/config.properties

并填入測試內容

test:
  name: zhangsan2222

2、將config.yml裝載進spring

public static void setConfig() {
        String configLocation = getProjectPath() + "/src/main/resources/config/config.yml";
        System.setProperty("spring.config.additional-location",configLocation);
    }

 public static String getProjectPath() {
        String basePath = ConfigFileUtil.class.getResource("").getPath();
        return basePath.substring(0, basePath.indexOf("/target"));
    }

3、實現(xiàn)配置監(jiān)聽

注: 利用hutool的WatchMonitor或者apache common io的文件監(jiān)聽即可實現(xiàn)

以apache common io為例

a、 業(yè)務pom文件引入common-io gav

<dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${common-io.version}</version>
        </dependency>

b、 自定義文件變化監(jiān)聽器

@Slf4j
public class ConfigPropertyFileAlterationListener extends FileAlterationListenerAdaptor {
    private ApplicationContext applicationContext;
    public ConfigPropertyFileAlterationListener(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
    @Override
    public void onStart(FileAlterationObserver observer) {
        super.onStart(observer);
    }
    @Override
    public void onDirectoryCreate(File directory) {
        super.onDirectoryCreate(directory);
    }
    @Override
    public void onDirectoryChange(File directory) {
       super.onDirectoryChange(directory);
    }
    @Override
    public void onDirectoryDelete(File directory) {
        super.onDirectoryDelete(directory);
    }
    @Override
    public void onFileCreate(File file) {
        super.onFileCreate(file);
    }
    @Override
    public void onFileChange(File file) {
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>> Monitor PropertyFile with path --> {}",file.getName());
        refreshConfig(file);
    }
    @Override
    public void onFileDelete(File file) {
        super.onFileDelete(file);
    }
    @Override
    public void onStop(FileAlterationObserver observer) {
        super.onStop(observer);
    }
    }

c、 啟動文件監(jiān)聽器

@SneakyThrows
    private static void monitorPropertyChange(FileMonitor fileMonitor, File file,ApplicationContext context){
        if(fileMonitor.isFileScanEnabled()) {
            String ext = "." + FilenameUtils.getExtension(file.getName());
            String monitorDir = file.getParent();
            //輪詢間隔時間
            long interval = TimeUnit.SECONDS.toMillis(fileMonitor.getFileScanInterval());
            //創(chuàng)建文件觀察器
            FileAlterationObserver observer = new FileAlterationObserver(
                    monitorDir, FileFilterUtils.and(
                    FileFilterUtils.fileFileFilter(),
                    FileFilterUtils.suffixFileFilter(ext)));
            observer.addListener(new ConfigPropertyFileAlterationListener(context));
            //創(chuàng)建文件變化監(jiān)聽器
            FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);
            //開始監(jiān)聽
            monitor.start();
        }
    }

4、監(jiān)聽文件變化,并實現(xiàn)PropertySource以及bean的刷新

@SneakyThrows
    private void refreshConfig(File file){
        ConfigurableEnvironment environment = applicationContext.getBean(ConfigurableEnvironment.class);
        MutablePropertySources propertySources = environment.getPropertySources();
        PropertySourceLoader propertySourceLoader = new YamlPropertySourceLoader();
        List<PropertySource<?>> propertySourceList = propertySourceLoader.load(file.getAbsolutePath(), applicationContext.getResource("file:"+file.getAbsolutePath()));
        for (PropertySource<?> propertySource : propertySources) {
           if(propertySource.getName().contains(file.getName())){
               propertySources.replace(propertySource.getName(),propertySourceList.get(0));
           }
        }
        RefreshBeanScopeHolder refreshBeanScopeHolder = applicationContext.getBean(RefreshBeanScopeHolder.class);
        List<String> strings = refreshBeanScopeHolder.refreshBean();
        log.info(">>>>>>>>>>>>>>> refresh Bean :{}",strings);
    }

5、測試

a、 編寫controller并將controller scope設置為我們自定義的scope

@RestController
@RequestMapping("test")
@RefreshBeanScope
public class TestController {
    @Value("${test.name: }")
    private String name;
    @GetMapping("print")
    public String print(){
        return name;
    }
}

原來的test.name內容如下

test:
  name: zhangsan2222

我們通過瀏覽器訪問

b、 此時我們不重啟服務器,并將test.name改為如下

test:
  name: zhangsan3333

此時發(fā)現(xiàn)控制臺會輸出我們的日志信息

通過瀏覽器再訪問

發(fā)現(xiàn)內容已經發(fā)生變化

附錄:自定義scope方法觸發(fā)時機

1、scope get方法

// Create bean instance.
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // Explicitly remove instance from singleton cache: It might have been put there
                            // eagerly by the creation process, to allow for circular reference resolution.
                            // Also remove any beans that received a temporary reference to the bean.
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
                else if (mbd.isPrototype()) {
                    // It's a prototype -> create a new instance.
                    Object prototypeInstance = null;
                    try {
                        beforePrototypeCreation(beanName);
                        prototypeInstance = createBean(beanName, mbd, args);
                    }
                    finally {
                        afterPrototypeCreation(beanName);
                    }
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }
                else {
                    String scopeName = mbd.getScope();
                    final Scope scope = this.scopes.get(scopeName);
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                    }
                    try {
                        Object scopedInstance = scope.get(beanName, () -> {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                afterPrototypeCreation(beanName);
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                    catch (IllegalStateException ex) {
                        throw new BeanCreationException(beanName,
                                "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                ex);
                    }
                }
            }
            catch (BeansException ex) {
                cleanupAfterBeanCreationFailure(beanName);
                throw ex;
            }

觸發(fā)時機就是在調用getBean時觸發(fā)

2、scope remove方法

@Override
    public void destroyScopedBean(String beanName) {
        RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        if (mbd.isSingleton() || mbd.isPrototype()) {
            throw new IllegalArgumentException(
                    "Bean name '" + beanName + "' does not correspond to an object in a mutable scope");
        }
        String scopeName = mbd.getScope();
        Scope scope = this.scopes.get(scopeName);
        if (scope == null) {
            throw new IllegalStateException("No Scope SPI registered for scope name '" + scopeName + "'");
        }
        Object bean = scope.remove(beanName);
        if (bean != null) {
            destroyBean(beanName, bean, mbd);
        }
    }

觸發(fā)時機實在調用destroyScopedBean方法

總結

如果對spring cloud RefreshScope有研究的話,就會發(fā)現(xiàn)上述的實現(xiàn)方式,就是RefreshScope的粗糙版本實現(xiàn)

demo鏈接

以上就是詳解spring項目中如何動態(tài)刷新bean的詳細內容,更多關于spring動態(tài)刷新bean的資料請關注腳本之家其它相關文章!

相關文章

  • JSON Web Token在登陸中的使用過程

    JSON Web Token在登陸中的使用過程

    這篇文章主要介紹了JSON Web Token在登陸中的使用過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • java17日期周期格式化處理方案

    java17日期周期格式化處理方案

    Java17引入了新的日期時間格式化模式B,用于表示一天中的時間段,如上午、下午、晚上等,該模式根據CLDR定義,在不同語言環(huán)境下,該模式的輸出結果會有所不同,本文給大家介紹java17日期周期格式化處理方案,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • java中實現(xiàn)token過期失效超時

    java中實現(xiàn)token過期失效超時

    在Java應用程序中,為了確保安全性和保護用戶數(shù)據,一種常見的做法是使用Token進行身份驗證和授權,Token是由服務器生成的具有一定時效的令牌,用于識別和驗證用戶身份,當Token失效后,用戶將無法再進行相關操作,從而提高系統(tǒng)的安全性
    2023-10-10
  • Java實現(xiàn)鼠標模擬與鍵盤映射

    Java實現(xiàn)鼠標模擬與鍵盤映射

    這篇文章主要為大家詳細介紹了Java實現(xiàn)鼠標模擬與鍵盤映射,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • 基于SpringBoot+Mybatis實現(xiàn)Mysql分表

    基于SpringBoot+Mybatis實現(xiàn)Mysql分表

    這篇文章主要為大家詳細介紹了基于SpringBoot+Mybatis實現(xiàn)Mysql分表的相關知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2025-04-04
  • @Autowired注解注入的xxxMapper報錯問題及解決

    @Autowired注解注入的xxxMapper報錯問題及解決

    這篇文章主要介紹了@Autowired注解注入的xxxMapper報錯問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Spring?cloud?OpenFeign中動態(tài)URl、動態(tài)傳遞接口地址代碼示例

    Spring?cloud?OpenFeign中動態(tài)URl、動態(tài)傳遞接口地址代碼示例

    openFeign是作為微服務之間調用的解決方案,每個微服務項目是必不可少的,下面這篇文章主要給大家介紹了關于Spring?cloud?OpenFeign中動態(tài)URl、動態(tài)傳遞接口地址的相關資料,需要的朋友可以參考下
    2024-02-02
  • 詳解spring-data-jpa中jpql的投影查詢

    詳解spring-data-jpa中jpql的投影查詢

    投影查詢,就是僅僅檢索表的部分字段。而不是粗暴的 SELECT * FROM...檢索出所有列數(shù)據,這篇文章主要介紹了spring-data-jpa中jpql的投影查詢,需要的朋友可以參考下
    2021-09-09
  • Spring Boot 中的靜態(tài)資源放置位置

    Spring Boot 中的靜態(tài)資源放置位置

    這篇文章主要介紹了Spring Boot 中的靜態(tài)資源到底要存放哪里,很多童鞋對這個問題很糾結,接下來通過本文給大家介紹下,需要的朋友可以參考下
    2019-04-04
  • Java 1.8使用數(shù)組實現(xiàn)循環(huán)隊列

    Java 1.8使用數(shù)組實現(xiàn)循環(huán)隊列

    這篇文章主要為大家詳細介紹了Java 1.8使用數(shù)組實現(xiàn)循環(huán)隊列,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-10-10

最新評論