众所周知,java编程中,更改了class文件的话需要重启JVM,从而导致Java的开发效率问题一直遭人诟病,不过,也可以用另外一种方法来避免这个问题,这个就是国外的大神写的JReloader,网址:http://code.google.com/p/jreloader/,用这个可以实时的修改class文件,而避免重启JVM,提高开发效率。
其实Jreloader所用到的技术大家想必都知道,java agent 和ClassLoader,大致原理是利用map来保持class文件和ClassLoader的映射,如果class文件改变了,那么就用classLoader重新加载新生成的class文件。
实现细节请大家到网站上看源码,相关知识点呢,也请大家谷哥去吧。
到这里你估计要开骂了,说了这么一大堆废话,谁TM不知道啊,莫急,以下进入正题。
如上所述,java agent和classloader不是什么新名词了,大家都懂的,jreloader的关键是我如何知道一个class改变了呢?Jreloader的源码中是开启了一个线程来做的,如下代码所示:
private ReloadThread thread;
public Transformer() {
String[] dirNames = System.getProperty("jreloader.dirs", ".").split("\\,");
for (String dirName : dirNames) {
File d = new File(dirName).getAbsoluteFile();
log.info("Added class dir '" + d.getAbsolutePath() + "'");
scan(d, d);
}
findGroups();
log.info(" \\-- Found " + entries.size() + " classes");
thread = new ReloadThread();
thread.start();
}
注意到 thread.start();了吗?这个在类实例化的时候就启动了,下面我们再看看ReloadThread的真身。
class ReloadThread extends Thread {
public ReloadThread() {
super("ReloadThread");
setDaemon(true);
setPriority(MAX_PRIORITY);
}
@Override
public void run() {
try {
sleep(5000);
} catch (InterruptedException e) {
}
while (true) {
try {
sleep(3000);
} catch (InterruptedException e) {
}
if (System.getProperty("jreloader.pauseReload") != null) {
continue;
}
log.debug("Checking changes...");
List<Entry> aux = new ArrayList<Entry>(entries.values());
for (Entry e : aux) {
if (e.isDirty()) {
e.forceDirty();
}
}
for (Entry e : aux) {
if (e.isDirty() && e.parent == null) {
log.debug("Reloading " + e.name);
try {
reload(e);
} catch (Throwable t) {
log.error("Could not reload " + e.name, t);
System.err
.println("[JReloader:ERROR] Could not reload class "
+ e.name.replace('/', '.'));
}
e.clearDirty();
}
}
}
}
通过while(true),就知道这个线程会循环执行,然后检查每一个class文件isDirty也就是lastModified时间,如果这个时间变了,则需要重新用classloader加载。
看到这里,你或许说,jdk7或者以上的Nio2.0中不是有WatchService能实现监测文件是否改变的功能吗?
是的,本文就是要讨论这一点。
首先为了向下兼容,我姑且直接检查JDK的版本,如果是JDK7以上版本,就用新的实现,代码如下
if (ReloaderUtils.isEqualsOrAboveJDK7()) {
try {
Path path = Paths.get(dirNames[0]);
watchDir = new WatchDir(path, true);
watchServiceThread = new WatchServiceThread();
watchServiceThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}else {
thread = new ReloadThread();
thread.start();
}
Path是新的NIO提供的一个类,类似于File,由于WatchService只能检测某一个具体文件夹下面的文件,如果文件夹下还有文件夹,它就爱莫能助了,所以我们必须要解决这个问题,这也就是WatchDir的由来,
WatchDir的代码如下:
private final WatchService watcher;
private final Map<WatchKey,Path> keys;
private final boolean recursive;
private boolean trace = false;
@SuppressWarnings("unchecked")
public static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
/**
* Register the given directory with the WatchService
*/
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_MODIFY);
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
}
}
}
keys.put(key, dir);
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
public void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException
{
register(dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Creates a WatchService and registers the given directory
*/
WatchDir(Path dir, boolean recursive) throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey,Path>();
this.recursive = recursive;
if (recursive) {
System.out.format("Scanning %s ...\n", dir);
registerAll(dir);
System.out.println("Done.");
} else {
register(dir);
}
// enable trace after initial registration
this.trace = true;
}
/**
* Process all events for keys queued to the watcher
*/
void processEvents() {
for (;;) {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> ev = cast(event);
Path name = ev.context();
Path child = dir.resolve(name);
// print out event
System.out.format("%s: %s\n", event.kind().name(), child);
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (recursive && (kind == ENTRY_CREATE)) {
try {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
registerAll(child);
}
} catch (IOException x) {
// ignore to keep sample readbale
}
}
}
// reset key and remove from set if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
break;
}
}
}
}
它通过扫描某个文件夹下面的所有子文件夹,把所有的文件夹加入到自己的监测管理中。
那WatchServiceThread又是干什么的呢,这是一个demon线程,下面是它的代码:
class WatchServiceThread extends Thread{
public WatchServiceThread() {
super("WatchServiceThread");
setDaemon(true);
setPriority(MAX_PRIORITY);
}
@Override
public void run() {
try {
processEvents();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
进入到主方法 processEvents();中
/**
* Process all events for keys queued to the watcher
* @throws InterruptedException
*/
private void processEvents() throws InterruptedException {
for (;;) {
Map<WatchKey,Path> keys = watchDir.getKeys();
WatchService wactherDir = watchDir.getWatcher();
// wait for key to be signalled
WatchKey key;
key = wactherDir.take();
// reset key and remove from set if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
break;
}
}
Path dir = keys.get(key);
if (dir == null) {
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> ev = WatchDir.cast(event);
Path modifiedFile = ev.context();
// final Path modifiedFile = name.context();
Path classFilePath = modifiedFile.getFileName();
if (classFilePath.isAbsolute()) {
continue;
}
if (!classFilePath.toString().endsWith(".class")) {
continue;
}
String javaClassFileNameWithNoSuffix = ReloaderUtils.getFileRealPath(key, ev, dirNames);
// classFileName.substring(0, classFileName.length() - 6);
Entry entry = entries.get(javaClassFileNameWithNoSuffix);
if (entry.parent == null) {
try {
reload(entry);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (UnmodifiableClassException e) {
e.printStackTrace();
}
}
Path child = dir.resolve(modifiedFile);
// print out event
System.out.format("%s: %s\n", event.kind().name(), child);
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (watchDir.isRecursive() && (kind == ENTRY_CREATE)) {
try {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
watchDir.registerAll(child);
}
} catch (IOException x) {
// ignore to keep sample readbale
}
}
}
}
}
看到没有,这是一个无限循环,而且 key = wactherDir.take();会阻塞, 所以必须开一个线程,不能让它阻塞主线程。那以后的事情都交给NIO2.0的API了
当有class文件被修改后,线程会被唤醒,就会走 for (WatchEvent<?> event: key.pollEvents()) {}逻辑,这个时候如果直接用Path.getAbsolute()我们只能拿到相对路径,如果我们要拿到绝对路径,只能变通一下
public static String getFileRealPath(WatchKey key, WatchEvent<Path> watchEvent){
Path dir = (Path)key.watchable();
String fullPath = dir.resolve(watchEvent.context()).toString();
用这样能拿到Path 的绝对路径,拿到一个更改过的class的绝对路径以后,那事情该怎么办就怎么办了,
Entry entry = entries.get(javaClassFileNameWithNoSuffix);
if (entry.parent == null) {
try {
reload(entry);。。。。
通过文件名拿到map中额Entry, 然后reload这个更改过的class,问题搞定。
虽然是个小功能,但是也code + debug也花费了大半天的时间,可能是能力不行吧。。。。。。。。。最后总结一下,就是把原来用线程获得更改过的class文件的方式换成用基于JDK7上 WatchService的方式来实现,因为Watchservice是基于事件处理,又是原生的API,理论上来说应该比用线程轮询的方式好点,不过仅是猜测,具体我也没测试过,如果哥们你想测试一下,一定要告诉我。
最后,文笔比较差,如果各位看官有什么看不懂的地方,可以直接私信我。
相关推荐
在Java编程领域,JDK 7引入了一个重要的更新——NIO2.0,也被称为“New I/O 2.0”或“AIO”(Asynchronous I/O...同时,NIO2.0的异步特性也为编写高并发、非阻塞的代码提供了便利,是现代Java开发中不可或缺的一部分。
java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现...
基于NIO2.0的网络框架可能会利用这些组件来实现高效、高性能的网络服务,例如TCP/UDP服务器,或者Websocket服务器。它可能会包含以下部分: - ** ServerBootstrap **:用于初始化服务器端的配置,如绑定端口、设置...
基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现...
# 基于Nio实现的Mycat 2.0数据库代理系统 ## 项目简介 Mycat 2.0是一个基于Nio实现的高性能数据库代理系统,旨在有效管理线程,解决高并发问题。它支持前后端共享buffer,支持全透传和半透传,极大地提升了内核性能...
Mycat 2.0是一个基于Nio实现的高性能数据库代理系统,旨在有效管理线程,解决高并发问题。它通过前后端共享buffer,支持全透传和半透传,极致提升内核性能,确保系统的稳定性和兼容性。 ## 项目的主要特性和功能 ...
基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例基于NIO的socket举例 基于NIO的socket举例
本项目"基于nio的简易聊天室"旨在通过NIO技术实现一个简单的聊天室服务端和客户端,其特点是有图形用户界面(GUI)供用户交互。 NIO的核心组件包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。在这...
总结,Java实现CMPP2.0协议涉及到网络编程、协议解析、多线程等多个技术领域,需要对TCP/IP通信、Java NIO、字符串编码有一定了解,并且能够根据具体需求进行业务逻辑的设计和实现。通过阅读和理解提供的Java源码,...
本主题“基于nio实现的多文件上传源码”探讨的是如何利用Java NIO来实现高效的多文件上传功能,尤其对于小文件数量较大的情况。 首先,理解NIO的基本概念是必要的。NIO中的“非阻塞”意味着当数据不可用时,读写...
基于事件的 NIO 多线程服务器
在“基于NIO简单实现网络聊天功能”这个项目中,我们将会涉及到以下几个关键知识点: 1. **缓冲区(Buffer)**:NIO的核心是缓冲区,它提供了更有效的方式管理数据。在Java NIO中,有如ByteBuffer、CharBuffer、...
基于NIO的多线程聊天系统,代码很少,很经典,51CTO网站上的代码。有登陆和聊天界面。代码结构层次清晰,系统只有6个类。
本资料主要涵盖了五个核心领域:Java并发(JUC)、非阻塞I/O(NIO)、Netty框架、Tomcat服务器优化以及Java虚拟机(JVM)调优。以下是这些主题的详细说明: 1. **Java并发(JUC - Java Concurrency Utilities)** ...
Java作为一门广泛使用的开发语言,提供了多种I/O(Input/Output)通信模型,包括传统的阻塞I/O(BIO)、非阻塞I/O(NIO)以及异步I/O(AIO)。这些通信模型在不同的场景下有着各自的优势,理解和掌握它们对于优化...
基于NIO的远程调用框架的设计与实现 master
在本项目"基于NIO的Java聊天室2"中,我们深入探讨了如何利用Java的非阻塞I/O(New Input/Output,NIO)框架来实现一个多人在线聊天室。NIO是一种高效的I/O模型,它允许程序在不阻塞主线程的情况下处理输入和输出操作...
在本文中,我们将深入探讨Java NIO.2中的文件监控机制,以及如何使用WatchService实现稳健的监控。 首先,WatchService是一个接口,它提供了一种注册Path(文件或目录路径)并监听特定事件(如文件创建、删除、修改...
`aws-lambda-swift-sprinter` 是一个项目,专门用于在AWS Lambda上运行Swift代码,它提供了对Swift-NIO 2.0的支持。Swift-NIO是一个高性能的事件驱动网络应用框架,适用于构建服务器端应用和服务。Swift-NIO 2.0版本...
在这个基于NIO非阻塞的Java聊天demo中,我们将会看到如何利用NIO实现一个支持单聊和群聊的应用。 首先,NIO的核心组件包括Channel、Buffer、Selector和Pipe。在传统的IO模型中,数据是从流的一端流向另一端,而在...