定义注解
package jelly.command.watcher.annotions;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FileWatcherListener {
String[] value();
}
注解信息实体类
package jelly.command.watcher.bean;
import java.lang.reflect.Method;
public class WatcherInfo {
private Object clazz;
private Method method;
public WatcherInfo(Object clazz, Method member) {
this.clazz = clazz;
this.method = member;
}
public Object getClazz() {
return clazz;
}
public void setClazz(Object clazz) {
this.clazz = clazz;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "WatcherInfo{" +
"bean=" + clazz +
", method=" + method +
'}';
}
}
注册监听
package jelly.command.watcher.core;
import jelly.command.watcher.bean.WatcherInfo;
import java.util.*;
public class FileWatcherRegister {
private static final Map<String, List<WatcherInfo>> watcherInfoMap = new HashMap<>();
public static void register(String path, WatcherInfo watcherInfo) {
List<WatcherInfo> watcherInfoList = watcherInfoMap.get(path);
if (watcherInfoList == null) {
watcherInfoList = new ArrayList<>();
}
watcherInfoList.add(watcherInfo);
watcherInfoMap.put(path,watcherInfoList);
}
public static Map<String, List<WatcherInfo>> getWatcherInfoMap() {
return watcherInfoMap;
}
}
监听核心类
package jelly.command.watcher.core;
import jelly.command.api.util.CyclicTask;
import jelly.command.watcher.observe.FileSubject;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
/**
* 文件监听任务,当监听到有新的文件创建时,通过主题通知所有已注册的观察者
*/
public class FileWatcherTask extends CyclicTask {
private static final Logger LOG = LoggerFactory.getLogger(FileWatcherTask.class);
private final String parentDirPath;
private WatchService watchService;
private final FileSubject observable;
public FileWatcherTask(String parentDirPath, FileSubject observable) {
super(1);
this.parentDirPath = parentDirPath;
this.observable = observable;
try {
this.watchService = FileSystems.getDefault().newWatchService();
Paths.get(parentDirPath).register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
} catch (IOException e) {
LOG.error("Failed to init watchService: " + e.getMessage());
}
}
@Override
public void runActualTask() {
WatchKey key;
try {
LOG.debug("watchService is waiting for {}", parentDirPath);
key = watchService.take();
LOG.debug("watchService take from {} :{} ", parentDirPath, key.toString());
for (WatchEvent<?> event : key.pollEvents()) {
String filePath = parentDirPath + File.separator + event.context();
LOG.debug("Found new File:{}", filePath);
File file = FileUtils.getFile(filePath);
observable.makeChanged(file);
}
boolean isKeyValid = key.reset();
if (!isKeyValid) {
LOG.error("key is inValid");
terminate();
}
} catch (InterruptedException e) {
LOG.error("runActualTask error : " + e.getMessage());
}
}
}
获取监听对象及方法并完成注册
package jelly.command.watcher.load;
import jelly.command.watcher.annotions.FileWatcherListener;
import jelly.command.watcher.bean.WatcherInfo;
import jelly.command.watcher.core.FileWatcherRegister;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.Set;
@Component
public class WatcherLoader implements BeanPostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(WatcherLoader.class);
private final Environment environment;
public WatcherLoader(Environment environment) {
this.environment = environment;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> targetClass = AopUtils.getTargetClass(bean);
Set<Method> configMembers = MethodIntrospector.selectMethods(targetClass, (ReflectionUtils.MethodFilter) method -> method.getAnnotation(FileWatcherListener.class) != null);
for (Method member : configMembers) {
FileWatcherListener centerConfig = member.getAnnotation(FileWatcherListener.class);
String[] value = centerConfig.value();
for (String path : value) {
path = isDynamicProperties(path);
LOG.debug("FileWatcher register : path 【{}】, beanName 【{}】, method 【{}】", path, bean.getClass().getName(), member.getName());
FileWatcherRegister.register(path, new WatcherInfo(bean, member));
}
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
private String isDynamicProperties(String path) {
if (path.startsWith("{") && path.endsWith("}")) {
path = environment.getProperty(path.replace("{", "").replace("}", ""));
}
return path;
}
}
通过观察者模式通知
package jelly.command.watcher.observe;
import jelly.command.watcher.bean.WatcherInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
/**
* 当监听的文件夹中有新创建文件时,被通知;
* 通过反射执行每个监听方法,且均为独立子线程,互不阻塞。
*/
public class FileObserver implements Observer {
private static final Logger LOG = LoggerFactory.getLogger(FileObserver.class);
private final List<WatcherInfo> watcherInfoList;
public FileObserver(List<WatcherInfo> watcherInfoList) {
this.watcherInfoList = watcherInfoList;
}
@Override
public void update(Observable observable, Object o) {
if (!(o instanceof File)) {
return;
}
File file = (File) o;
for (WatcherInfo watcherInfo : watcherInfoList) {
Object bean = watcherInfo.getClazz();
Method method = watcherInfo.getMethod();
new Thread(() -> {
try {
method.invoke(bean, file);
LOG.debug("exe method : {}#{}",bean.getClass().getName(),method.getName());
} catch (IllegalAccessException | InvocationTargetException e) {
LOG.error("exe method error: {}",e.getMessage());
}
}).start();
}
}
}
package jelly.command.watcher.observe;
import java.io.File;
import java.util.Observable;
public class FileSubject extends Observable {
public void makeChanged(File file){
setChanged();
notifyObservers(file);
}
}
初始化
package cetc54.command.watcher.runner;
import jelly.command.watcher.bean.WatcherInfo;
import jelly.command.watcher.core.FileWatcherTask;
import jelly.command.watcher.core.FileWatcherRegister;
import jelly.command.watcher.observe.FileObserver;
import jelly.command.watcher.observe.FileSubject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 在项目启动加载完成之后,根据已注册的路径,创建文件监听任务
*/
@Component
public class WatcherRunner implements CommandLineRunner {
private static final Logger LOG = LoggerFactory.getLogger(WatcherRunner.class);
private final ExecutorService executorService = Executors.newCachedThreadPool();
@Override
public void run(String... args) throws Exception {
Map<String, List<WatcherInfo>> watcherInfoMap = FileWatcherRegister.getWatcherInfoMap();
LOG.debug("watcherInfoMap is {}",watcherInfoMap.toString());
watcherInfoMap.forEach((path, watcherInfoList) -> {
FileSubject fileSubject = new FileSubject();
FileObserver fileObserver = new FileObserver(watcherInfoList);
LOG.debug("{} add fileObserver:{}",path,watcherInfoList.toString());
fileSubject.addObserver(fileObserver);
FileWatcherTask fileWatcherTask = new FileWatcherTask(path, fileSubject);
executorService.submit(fileWatcherTask);
});
}
}
测试类
import jelly.command.watcher.annotions.FileWatcherListener;
import org.springframework.stereotype.Component;
import java.io.File;
@Component
public class MyWatcherDemo {
@FileWatcherListener({"{path}"})
public void test01(File file) {
}
@FileWatcherListener({"D:\\temp\\"})
public void test02(File file) {
}
}