`
yushl
  • 浏览: 11584 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

基于NIO2.0的WatchService实现JReloader,不重启jvm 的debug

    博客分类:
  • java
 
阅读更多

       众所周知,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,理论上来说应该比用线程轮询的方式好点,不过仅是猜测,具体我也没测试过,如果哥们你想测试一下,一定要告诉我。

    最后,文笔比较差,如果各位看官有什么看不懂的地方,可以直接私信我。

 

 

分享到:
评论

相关推荐

    利用JDK7的NIO2.0进行I/O读写和监视

    在Java编程领域,JDK 7引入了一个重要的更新——NIO2.0,也被称为“New I/O 2.0”或“AIO”(Asynchronous I/O...同时,NIO2.0的异步特性也为编写高并发、非阻塞的代码提供了便利,是现代Java开发中不可或缺的一部分。

    java基于NIO实现Reactor模型源码.zip

    java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现...

    after:基于NIO2.0的网络框架

    基于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实现五子棋游戏.zip 基于Java NIO实现...

    (源码)基于Nio实现的Mycat 2.0数据库代理系统.zip

    # 基于Nio实现的Mycat 2.0数据库代理系统 ## 项目简介 Mycat 2.0是一个基于Nio实现的高性能数据库代理系统,旨在有效管理线程,解决高并发问题。它支持前后端共享buffer,支持全透传和半透传,极大地提升了内核性能...

    (源码)基于Nio的Mycat 2.0数据库代理系统.zip

    Mycat 2.0是一个基于Nio实现的高性能数据库代理系统,旨在有效管理线程,解决高并发问题。它通过前后端共享buffer,支持全透传和半透传,极致提升内核性能,确保系统的稳定性和兼容性。 ## 项目的主要特性和功能 ...

    基于NIO的socket举例

    基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例基于NIO的socket举例 基于NIO的socket举例

    基于nio的简易聊天室

    本项目"基于nio的简易聊天室"旨在通过NIO技术实现一个简单的聊天室服务端和客户端,其特点是有图形用户界面(GUI)供用户交互。 NIO的核心组件包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。在这...

    cmpp2.0 java实现

    总结,Java实现CMPP2.0协议涉及到网络编程、协议解析、多线程等多个技术领域,需要对TCP/IP通信、Java NIO、字符串编码有一定了解,并且能够根据具体需求进行业务逻辑的设计和实现。通过阅读和理解提供的Java源码,...

    基于nio实现的多文件上传源码

    本主题“基于nio实现的多文件上传源码”探讨的是如何利用Java NIO来实现高效的多文件上传功能,尤其对于小文件数量较大的情况。 首先,理解NIO的基本概念是必要的。NIO中的“非阻塞”意味着当数据不可用时,读写...

    基于事件的 NIO 多线程服务器

    基于事件的 NIO 多线程服务器

    基于NIO简单实现网络聊天功能

    在“基于NIO简单实现网络聊天功能”这个项目中,我们将会涉及到以下几个关键知识点: 1. **缓冲区(Buffer)**:NIO的核心是缓冲区,它提供了更有效的方式管理数据。在Java NIO中,有如ByteBuffer、CharBuffer、...

    基于NIO的多线程聊天系统

    基于NIO的多线程聊天系统,代码很少,很经典,51CTO网站上的代码。有登陆和聊天界面。代码结构层次清晰,系统只有6个类。

    学习juc、nio、netty、tomcat调优、jvm调优-Advanced-JAVA.zip

    本资料主要涵盖了五个核心领域:Java并发(JUC)、非阻塞I/O(NIO)、Netty框架、Tomcat服务器优化以及Java虚拟机(JVM)调优。以下是这些主题的详细说明: 1. **Java并发(JUC - Java Concurrency Utilities)** ...

    基于java的BIO、NIO、AIO通讯模型代码实现

    Java作为一门广泛使用的开发语言,提供了多种I/O(Input/Output)通信模型,包括传统的阻塞I/O(BIO)、非阻塞I/O(NIO)以及异步I/O(AIO)。这些通信模型在不同的场景下有着各自的优势,理解和掌握它们对于优化...

    基于NIO的远程调用框架的设计与实现 master

    基于NIO的远程调用框架的设计与实现 master

    基于NIO的Java聊天室2

    在本项目"基于NIO的Java聊天室2"中,我们深入探讨了如何利用Java的非阻塞I/O(New Input/Output,NIO)框架来实现一个多人在线聊天室。NIO是一种高效的I/O模型,它允许程序在不阻塞主线程的情况下处理输入和输出操作...

    java jre7里的nio实现稳健监控

    在本文中,我们将深入探讨Java NIO.2中的文件监控机制,以及如何使用WatchService实现稳健的监控。 首先,WatchService是一个接口,它提供了一种注册Path(文件或目录路径)并监听特定事件(如文件创建、删除、修改...

    aws-lambda-swift-sprinter:具有swift-nio 2.0支持的AWS Lambda Swift自定义运行时

    `aws-lambda-swift-sprinter` 是一个项目,专门用于在AWS Lambda上运行Swift代码,它提供了对Swift-NIO 2.0的支持。Swift-NIO是一个高性能的事件驱动网络应用框架,适用于构建服务器端应用和服务。Swift-NIO 2.0版本...

    基于NIO非阻塞的java聊天demo(支持单聊和群聊)

    在这个基于NIO非阻塞的Java聊天demo中,我们将会看到如何利用NIO实现一个支持单聊和群聊的应用。 首先,NIO的核心组件包括Channel、Buffer、Selector和Pipe。在传统的IO模型中,数据是从流的一端流向另一端,而在...

Global site tag (gtag.js) - Google Analytics