设计模式:使用观察者实现配置修改准实时生效

在开发过程中,经常需要修改配置文件来调整应用程序的行为。但是,如果每次修改配置文件都需要重启应用程序,那么将会浪费很多时间。为了解决这个问题,我们可以使用观察者模式来监听配置文件的变化,并借助apache.commons库来实现配置修改准实时生效,而不用重启SpringBoot应用。

目录

观察者模式简介

实现步骤

1. 添加依赖

2. 主题配置

3. 创建观察者


观察者模式简介

在观察者模式中,有两个核心角色:主题和观察者。主题是被观察的对象,观察者是观察主题的对象。当主题状态发生变化时,它会通知所有的观察者,让它们自动更新。这种模式中,主题和观察者是松耦合的,它们之间没有直接的依赖关系。这样,当我们需要增加新的观察者时,只需要实现观察者接口即可,不需要修改主题的代码。

实现步骤

1. 添加依赖

首先,在pom.xml文件中添加apache.commons库的依赖:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.8</version>
</dependency>

2. 主题配置

然后,我们配置主题,使用apache.commons库中的FileAlterationMonitor类来实现对文件的变化监听,检查文件变化的时间间隔使用默认值10秒。

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.HiddenFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.monitor.FileAlterationListener;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.io.File;

@Slf4j
@Configuration
public class FileMonitorConfig {
    private FileAlterationMonitor monitor;

    @PostConstruct
    public void init() {
        monitor = new FileAlterationMonitor();
        try {
            monitor.start();
        } catch (Exception e) {
            log.warn("{}", e.getLocalizedMessage());
        }
    }

    public void addFileAlteration(String fileDir, String fileName, FileAlterationListener listener) {
        IOFileFilter directories = FileFilterUtils.and(FileFilterUtils.directoryFileFilter(), HiddenFileFilter.VISIBLE);
        // FileFilterUtils.suffixFileFilter(fileSuffix) 可监听具有某个文件名后缀的一些文件
        // IOFileFilter files = FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter(fileSuffix));
        IOFileFilter files = FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.nameFileFilter(fileName));
        IOFileFilter filter = FileFilterUtils.or(directories, files);
        FileAlterationObserver observer = new FileAlterationObserver(fileDir, filter);
        observer.addListener(listener);
        try {
            log.info("监听{}{}{}文件变化", fileDir, fileDir.endsWith(File.separator) ? "" : File.separatorChar, fileName);
            monitor.addObserver(observer);
        } catch (Exception e) {
            log.warn("{}", e.getLocalizedMessage());
        }
    }
}

3. 创建观察者

最后,创建配置文件的观察者。在观察者的事件处理接口实现中,解析yml配置文件并更新当前应用环境配置。通过调试在MutablePropertySources mps后设置断点,可以看到mps中有多个PropertySource,有来自本地resources/config目录的,如果使用配置中心,有来自配置中心的,如果配置了spring.config.additional-location,有来自附加配置目录的。

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

@Slf4j
@Component
public class AppConfigMonitor implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Autowired
    ConfigurableEnvironment cfgableEnv;
    @Autowired
    FileMonitorConfig fileMonitorConfig;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        AppConfigMonitor.applicationContext = applicationContext;
    }

    public static String getConfigAddLocation() {
        return applicationContext.getEnvironment().getProperty("spring.config.additional-location");
    }

    @PostConstruct
    public void initEnv() {
        String dynamicConfigDir = null;
        String cfgAddDir = getConfigAddLocation();
        if (StringUtils.isNotBlank(cfgAddDir)) {
            if (!new File(cfgAddDir).exists()) {
                log.warn("spring.config.additional-location directory {} does not exists", cfgAddDir);
            }
            dynamicConfigDir = cfgAddDir;
        } else {
            ClassPathResource cpr = new ClassPathResource("config");
            if (!cpr.exists()) {
                return;
            }
            try {
                if (ResourceUtils.URL_PROTOCOL_FILE.equals(cpr.getURL().getProtocol())) {
                    //本地调试,监听本地resources/config目录的配置文件
                    dynamicConfigDir = cpr.getFile().getPath();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (dynamicConfigDir != null) {
            String actProf = applicationContext.getEnvironment().getActiveProfiles()[0];
            fileMonitorConfig.addFileAlteration(dynamicConfigDir, "application-" + actProf + ".yml", new YamlAlterationListener());
        }
    }

    public class YamlAlterationListener extends FileAlterationListenerAdaptor {
        @Override
        public void onFileCreate(File file) {
            log.info("load {}", file.getAbsolutePath());
            updateEnvironment(file);
        }

        @Override
        public void onFileChange(File file) {
            log.info("update {}", file.getAbsolutePath());
            updateEnvironment(file);
        }

        @Override
        public void onFileDelete(File file) {
            log.info("delete {}", file.getAbsolutePath());
            MutablePropertySources mps = cfgableEnv.getPropertySources();
            mps.stream().filter(p -> isFilePropertySource(p.getName(), file)).forEach(p -> mps.remove(p.getName()));
        }

        private void updateEnvironment(File file) {
            YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
            yaml.setResources(new FileSystemResource(file));
            Properties props;
            try {
                props = yaml.getObject();
            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }

            MutablePropertySources mps = cfgableEnv.getPropertySources();
            List<PropertySource<?>> psList = mps.stream().filter(p -> isFilePropertySource(p.getName(), file)).collect(Collectors.toList());
            if (psList.isEmpty()) {
                mps.addLast(new PropertiesPropertySource(propertiesPropertySourceName(file.getAbsolutePath()), props));
            } else {
                psList.stream().forEach(p -> mps.replace(p.getName(), new PropertiesPropertySource(p.getName(), props)));
            }
        }

        // 在MutablePropertySources中,yml配置文件的propertySourceName
        private String propertiesPropertySourceName(String filePath) {
            return "applicationConfig: [file:" + filePath + "]";
        }

        // 判断MutablePropertySources中某个PropertySource是否来自文件file
        private boolean isFilePropertySource(String propertySourceName, File file) {
            if (propertySourceName.contains(file.getName())) {
                if (propertySourceName.contains("[file:" + file.getAbsolutePath() + "]")) {
                    return true;
                }
                if (file.getAbsolutePath().contains(":\\")) {
                    String winPath1 = StringUtils.replace(file.getAbsolutePath(), "\\", "/");
                    if (propertySourceName.contains("[file:" + winPath1 + "]")) {
                        return true;
                    }
                } else {
                    String relativePath = "." + StringUtils.replace(file.getAbsolutePath(), System.getProperty("user.home"), "", 1);
                    if (propertySourceName.contains("[file:" + relativePath + "]")) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}

也可以为properties配置文件创建观察者,使用以下方式解析:

    Properties properties = new Properties();
    fileInputStream = new FileInputStream(file);
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream, StandardCharsets.UTF_8));
    properties.load(bufferedReader);

现在,当我们修改配置文件时,应用程序会自动重新加载配置信息,不需要重启应用程序就可以获取到新的配置值。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

创意程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值