参考: JFianl 2.2
用JFianl开发web项目的时候,发现修改文件后会重新启动,感到很好奇,所以研究一下实现的方法
实现的基本步骤
- 找到项目的根目录
- 将项目文件中的所有文件的最近修改时间和文件大小在Map中进行保存
- 通过java的timer定时器保存文件并检查map是否一致
- 不一致则调用服务器的stop()方法,然后重新初始化服务器,再重新start()
Scanner扫描类
public abstract class Scanner {
//定时器
private Timer timer;
//定时任务
private TimerTask task;
//项目根目录
private File rootDir;
//定时器时间间隔
private int interval;
//是否正在运行
private boolean isRunning;
//准备扫描的文件
//key:文件的完全路径 value:文件的简略表达(最近修改时间+文件大小)
private final Map<String, TimeSize> preScan = new HashMap<String, TimeSize>();
//正在扫描的文件
//key:文件的完全路径 value:文件的简略表达(最近修改时间+文件大小)
private final Map<String, TimeSize> curScan = new HashMap<String, TimeSize>();
//构造器
public Scaner(String rootDir, int interval) {
if (rootDir == null || "".equals(rootDir.trim()))
throw new IllegalArgumentException("根目录不能为空");
this.rootDir = new File(rootDir);
if (!this.rootDir.isDirectory())
throw new IllegalArgumentException("文件夹" + rootDir + "不存在");
if (interval <= 0)
throw new IllegalArgumentException("扫描时隔参数必须大于0");
this.interval = interval;
}
//子类实现的扫描发生改变后的执行动作
public abstract void onChange();
//工作方法,具体的步骤都在这里
private void working() {
//1.首先进行扫描
scan(rootDir);
//2.扫描完成后进行比较,如果有改动则调用onChange()方法
compare();
//3.没有改动则把准备扫描的map清空
preScan.clear();
//4.正在扫描的文件放入
preScan.putAll(curScan);
//5.清空正在扫描文件的map
curScan.clear();
}
//开启扫描,就是建立一个定时任务,执行working()方法
public void start() {
if (!isRunning) {
timer = new Timer("Starlight Scanner", true);
task = new TimerTask() {
@Override
public void run() {
working();
}};
timer.schedule(task, 1010L * interval, 1010L * interval);
isRunning = true;
}
}
//停止扫描,停止定时器和任务
public void stop() {
if (isRunning) {
timer.cancel();
task.cancel();
isRunning = false;
}
}
//比较的方法,就是将两个map进行equals比较,不一致就说明有文件进行了修改
//出现修改就执行onChange()方法
private void compare() {
if (preScan.size() == 0)
return;
if (!preScan.equals(curScan))
onChange();
}
//递归扫描根目录中所有的文件,将其放入curScan中
private void scan(File file) {
if (file == null || !file.exists())
return;
if (file.isFile()) {
try {
//这里的getCanonicalPath()就是获取文件的完全路径
//TimeSize是一个文件的简略表达的类,可以表示一个文件的,在Scanner类的下方给出了这个类的定义
curScan.put(file.getCanonicalPath(), new TimeSize(file.lastModified(), file.length()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if (file.isDirectory()) {
File[] fs = file.listFiles();
if (fs != null)
for (File f : fs)
scan(f);
}
}
}
//此类是文件类的简略表达
class TimeSize {
/**
* @fields time 文件的最近修改时间
*/
final long time;
/**
* @fields size 文件的大小
*/
final long size;
public TimeSize(long time, long size) {
super();
this.time = time;
this.size = size;
}
@Override
public int hashCode() {
return (int)(time ^ size);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TimeSize) {
TimeSize ts = (TimeSize)obj;
return this.time == ts.time && this.size == ts.size;
}
return false;
}
@Override
public String toString() {
return "TimeSize [time=" + time + ", size=" + size + "]";
}
}
JettyServer类
当扫描器发现文件发生变化,就new出一个匿名内部类,执行服务器重启任务
//配置自动扫描
if (scanIntervalSeconds > 0) {
Scanner scanner = new Scanner(PathUtil.getWebRootPath(), scanIntervalSeconds) {
@Override
public void onChange() {
System.err.println("\n正在重新加载修改文件...");
try {
webApp.stop();
JFianlClassLoader loader = new JFianlClassLoader(webApp, getClassPath());
webApp.setClassLoader(loader);
webApp.start();
System.err.println("重新加载完成");
} catch (Exception e) {
System.err.println("经过文件的修改后项目无法完成重新配置和重新启动:" + e.getMessage());
//TODO
}
}
};
System.out.println("已完成检查时间为:" + scanIntervalSeconds + "秒的扫描器");
scanner.start();
}