文章11 | 阅读 5962 | 点赞0
当monitorInterval属性的值不为null,并且配置文件是存在的时候,Log4J2也有一套机制来实现对配置文件的热更新,简单说也就是当文件被改变的时候,Log4j2会动态的加载最新的配置。
以XmlConfiguration为例:
//省略部分解析配置的代码
if ("monitorInterval".equalsIgnoreCase(key)) {
final int intervalSeconds = Integer.parseInt(value);
if (intervalSeconds > 0) {
//获取WatchManager, 并设置配置监控间隔
getWatchManager().setIntervalSeconds(intervalSeconds);
//如果当前配置文件不为null,则创建配置文件观察者
if (configFile != null) {
final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
//添加文件监控
getWatchManager().watchFile(configFile, watcher);
}
}
}
作用:
负责管理要监控的文件与文件监控器
定时扫描要监控的文件,并通过FileMonitor判断文件是否被修改
监控管理
private final ConcurrentMap<File, FileMonitor> watchers = new ConcurrentHashMap<>();
public void watchFile(final File file, final FileWatcher watcher) {
watchers.put(file, new FileMonitor(file.lastModified(), watcher));
}
public void start() {
//设置状态为已启动
super.start();
//intervalSeconds即monitorInterval的值
if (intervalSeconds > 0) {
//启动定时器
future = scheduler.scheduleWithFixedDelay(new WatchRunnable(), intervalSeconds, intervalSeconds,
TimeUnit.SECONDS);
}
}
private class WatchRunnable implements Runnable {
@Override
public void run() {
//遍历要监控的文件列表
for (final Map.Entry<File, FileMonitor> entry : watchers.entrySet()) {
final File file = entry.getKey();
final FileMonitor fileMonitor = entry.getValue();
//获取文件最新的更改时间
final long lastModfied = file.lastModified();
//判断文件是否修改
if (fileModified(fileMonitor, lastModfied)) {
logger.info("File {} was modified on {}, previous modification was {}", file, lastModfied, fileMonitor.lastModifiedMillis);
fileMonitor.lastModifiedMillis = lastModfied; //通过fileWatcher文件已被需改
fileMonitor.fileWatcher.fileModified(file);
}
}
}
//文件最近修改时间如果大于上次修改时间,则认定文件被修改
private boolean fileModified(final FileMonitor fileMonitor, final long lastModifiedMillis) {
return lastModifiedMillis != fileMonitor.lastModifiedMillis;
}
}
private class FileMonitor {
private final FileWatcher fileWatcher;
private long lastModifiedMillis;
public FileMonitor(final long lastModifiedMillis, final FileWatcher fileWatcher) {
this.fileWatcher = fileWatcher;
this.lastModifiedMillis = lastModifiedMillis;
}
@Override
public String toString() {
return "FileMonitor [fileWatcher=" + fileWatcher + ", lastModifiedMillis=" + lastModifiedMillis + "]";
}
}
@Override
public void fileModified(final File file) {
//根据文件名获取脚本的执行器
final ScriptRunner runner = scriptRunners.get(file.toString());
if (runner == null) {
logger.info("{} is not a running script");
return;
}
//获取执行引擎
final ScriptEngine engine = runner.getScriptEngine();
//获取脚本信息,如语言,内容,名字
final AbstractScript script = runner.getScript();
//根据是否有KEY_THREADING参数会有不同的运行机制
//将更新的脚本放入到Map中,等待被执行
if (engine.getFactory().getParameter(KEY_THREADING) == null) {
scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script));
} else {
scriptRunners.put(script.getName(), new MainScriptRunner(engine, script));
}
}
ps: 脚本的作用及用法可以自行看看官方解释 - scripts 部分。
public void fileModified(final File file) {
//遍历watcher中的所有监听器,并启动相应的线程来通知监听器执行动作,其实这里可以理解成有ConfiguratonFileWatcher是对多个FileWtacher的一个包装
for (final ConfigurationListener configurationListener : configurationListeners) {
final Thread thread = threadFactory.newThread(new ReconfigurationRunnable(configurationListener, reconfigurable));
thread.start();
}
}
private static class ReconfigurationRunnable implements Runnable {
private final ConfigurationListener configurationListener;
private final Reconfigurable reconfigurable;
public ReconfigurationRunnable(final ConfigurationListener configurationListener, final Reconfigurable reconfigurable) {
this.configurationListener = configurationListener;
this.reconfigurable = reconfigurable;
}
@Override
public void run() {
configurationListener.onChange(reconfigurable);
}
}
public synchronized void onChange(final Reconfigurable reconfigurable) {
LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
//通过reconfigurable获取最新的配置
final Configuration newConfig = reconfigurable.reconfigure();
if (newConfig != null) {
//更新LoggerContext的配置,也就是整个日志系统的配置
setConfiguration(newConfig);
LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this);
} else {
LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this);
}
}
Reconfigurable: 实现对配置文件的重新配置
实现类有以下5种,其实也就是配置文件的解析类
public Configuration reconfigure() {
try {
//将配置源文件转化为输入流
final ConfigurationSource source = getConfigurationSource().resetInputStream();
if (source == null) {
return null;
}
//重新生成XmlConfiguration
final XmlConfiguration config = new XmlConfiguration(getLoggerContext(), source);
//判断新生成的配置信息是否合法:简单验证一下是否有root节点
return config.rootElement == null ? null : config;
} catch (final IOException ex) {
LOGGER.error("Cannot locate file {}", getConfigurationSource(), ex);
}
return null;
}
学习这套监控机制还是挺受益的,可以联想到其他的很多监控场景,譬如说服务挂掉之后自动重启等等,相似的套路。区别在于,要监控的是进程的状态(目标),当进程不存在的时候(事件),就重新执行进程的启动命令(事件对应的动作)。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/sweetyi/article/details/105038162
内容来源于网络,如有侵权,请联系作者删除!