怎么样热部署?

什么叫热部署?

jvm已经启动,修改了代码之后,不用重启jvm,编译代码之后重新加载一个新的class文件,直接生效

一般在线上关闭,在开发调试的时候打开热部署;因为热部署检测class文件修改,自己加载,不可控,有可能出现不可知的问题

实现热部署有两个关键: 一是发现文件修改了,二是打破两亲委派模式,自定义加载自己的类

  1. 加载spring-boot-devtools/META-INF/spring.factories中,LocalDevtoolsAutoConfiguration类
  2. 注入了一个类FileSystemWatcherFactory,文件修改监听器工厂;一个文件修改监听器
           @Bean
        FileSystemWatcherFactory fileSystemWatcherFactory() {
            return this::newFileSystemWatcher;
        }

    private FileSystemWatcher newFileSystemWatcher() {
            Restart restartProperties = this.properties.getRestart();
            FileSystemWatcher watcher = new FileSystemWatcher(true, restartProperties.getPollInterval(),
                    restartProperties.getQuietPeriod());
            String triggerFile = restartProperties.getTriggerFile();
            if (StringUtils.hasLength(triggerFile)) {
                watcher.setTriggerFilter(new TriggerFileFilter(triggerFile));
            }
            List additionalPaths = restartProperties.getAdditionalPaths();
            for (File path : additionalPaths) {
                watcher.addSourceFolder(path.getAbsoluteFile());
            }
            return watcher;
        }

        @Bean
        @ConditionalOnMissingBean
        ClassPathFileSystemWatcher classPathFileSystemWatcher(FileSystemWatcherFactory fileSystemWatcherFactory,
                ClassPathRestartStrategy classPathRestartStrategy) {
            URL[] urls = Restarter.getInstance().getInitialUrls();
            ClassPathFileSystemWatcher watcher = new ClassPathFileSystemWatcher(fileSystemWatcherFactory,
                    classPathRestartStrategy, urls);
            watcher.setStopWatcherOnRestart(true);
            return watcher;
        }

//在容器实例化完成之后,执行bean生命周期方法
public class ClassPathFileSystemWatcher implements InitializingBean, DisposableBean, ApplicationContextAware {
@Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.restartStrategy != null) {
            FileSystemWatcher watcherToStop = null;
            if (this.stopWatcherOnRestart) {
                watcherToStop = this.fileSystemWatcher;
            }
//-----------加入了一个文件修改监听器-------------------------
            this.fileSystemWatcher.addListener(
                    new ClassPathFileChangeListener(this.applicationContext, this.restartStrategy, watcherToStop));
        }
//-----------启动监听-------------------------------------------------
        this.fileSystemWatcher.start();
    }

    @Override
    public void destroy() throws Exception {
        this.fileSystemWatcher.stop();
    }
}

public class FileSystemWatcher {
     public void start() {
        synchronized (this.monitor) {
            saveInitialSnapshots();
            if (this.watchThread == null) {
                Map localFolders = new HashMap<>(this.folders);
                this.watchThread = new Thread(new Watcher(this.remainingScans, new ArrayList<>(this.listeners),
                        this.triggerFilter, this.pollInterval, this.quietPeriod, localFolders));
                this.watchThread.setName("File Watcher");
                this.watchThread.setDaemon(this.daemon);
// -------------------------这里启动了Watcher线程,准备执行run()方法------------------
                this.watchThread.start();
            }
        }
    }
               @Override
        public void run() {
            int remainingScans = this.remainingScans.get();
            while (remainingScans > 0 || remainingScans == -1) {
                try {
                    if (remainingScans > 0) {
                        this.remainingScans.decrementAndGet();
                    }
                    scan();
                }
                catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
                remainingScans = this.remainingScans.get();
            }
        }

        private void scan() throws InterruptedException {
            Thread.sleep(this.pollInterval - this.quietPeriod);
            Map previous;
            Map current = this.folders;
            
                        //如果没有文件修改,一个在休眠循环
                    do {
                previous = current;
                current = getCurrentSnapshots();
                Thread.sleep(this.quietPeriod);
            }while (isDifferent(previous, current));
            if (isDifferent(this.folders, current)) {
    //--------------------------发现在有文件修改了,就更新
                updateSnapshots(current.values());
            }
        }

        private boolean isDifferent(Map previous, Map current) {
            if (!previous.keySet().equals(current.keySet())) {
                return true;
            }
            for (Map.Entry entry : previous.entrySet()) {
                FolderSnapshot previousFolder = entry.getValue();
                FolderSnapshot currentFolder = current.get(entry.getKey());
                if (!previousFolder.equals(currentFolder, this.triggerFilter)) {
                    return true;
                }
            }
            return false;
        }

        private Map getCurrentSnapshots() {
            Map snapshots = new LinkedHashMap<>();
            for (File folder : this.folders.keySet()) {
                snapshots.put(folder, new FolderSnapshot(folder));
            }
            return snapshots;
        }

        private void updateSnapshots(Collection snapshots) {
            Map updated = new LinkedHashMap<>();
            Set changeSet = new LinkedHashSet<>();
            for (FolderSnapshot snapshot : snapshots) {
                FolderSnapshot previous = this.folders.get(snapshot.getFolder());
                updated.put(snapshot.getFolder(), snapshot);
                ChangedFiles changedFiles = previous.getChangedFiles(snapshot, this.triggerFilter);
                if (!changedFiles.getFiles().isEmpty()) {
                    changeSet.add(changedFiles);
                }
            }
            if (!changeSet.isEmpty()) {
                              //发布文件修改事件
                fireListeners(Collections.unmodifiableSet(changeSet));
            }
            this.folders = updated;
        }

        private void fireListeners(Set changeSet) {
            for (FileChangeListener listener : this.listeners) {
 //---------这里调用 ClassPathFileChangeListener#onchange()------
                listener.onChange(changeSet);
            }
        }
}


class ClassPathFileChangeListener{

       @Override
    public void onChange(Set changeSet) {
        boolean restart = isRestartRequired(changeSet);
        publishEvent(new ClassPathChangedEvent(this, changeSet, restart));
    }

    private void publishEvent(ClassPathChangedEvent event) {
        this.eventPublisher.publishEvent(event);
        if (event.isRestartRequired() && this.fileSystemWatcherToStop != null) {
            this.fileSystemWatcherToStop.stop();
        }
    }}
  1. 接收文件修改事件Event的处理类
static class LiveReloadServerEventListener {
        private final OptionalLiveReloadServer liveReloadServer;

        LiveReloadServerEventListener(OptionalLiveReloadServer liveReloadServer) {
            this.liveReloadServer = liveReloadServer;
        }

        @EventListener
        public void onContextRefreshed(ContextRefreshedEvent event) {
            this.liveReloadServer.triggerReload();
        }

//监听到了文件修改事件
        @EventListener
        public void onClassPathChanged(ClassPathChangedEvent event) {
            if (!event.isRestartRequired()) {
                this.liveReloadServer.triggerReload();
            }

        }
    }
//源码是这个
@Bean
        ApplicationListener restartingClassPathChangedEventListener(
                FileSystemWatcherFactory fileSystemWatcherFactory) {
            return (event) -> {
                if (event.isRestartRequired()) {
//重启容器
                    Restarter.getInstance().restart(new FileWatchingFailureHandler(fileSystemWatcherFactory));
                }
            };
        }


public class Restarter{
public void restart(FailureHandler failureHandler) {
        if (!this.enabled) {
            this.logger.debug("Application restart is disabled");
            return;
        }
        this.logger.debug("Restarting application");
        getLeakSafeThread().call(() -> {
//先停止,调用ApplicationContext.stop()
            Restarter.this.stop();
//再启动,通过反射把Main方法再执行一次
            Restarter.this.start(failureHandler);
            return null;
        });
    }

protected void stop() throws Exception {
        this.logger.debug("Stopping application");
        this.stopLock.lock();
        try {
            for (ConfigurableApplicationContext context : this.rootContexts) {
                context.close();
                this.rootContexts.remove(context);
            }
            cleanupCaches();
            if (this.forceReferenceCleanup) {
                forceReferenceCleanup();
            }
        }
        finally {
            this.stopLock.unlock();
        }
//通知jvm可以gc
        System.gc();
        System.runFinalization();
    }

protected void start(FailureHandler failureHandler) throws Exception {
        do {
//开始启动
            Throwable error = doStart();
            if (error == null) {
                return;
            }
            if (failureHandler.handle(error) == Outcome.ABORT) {
                return;
            }
        }
        while (true);
    }

private Throwable doStart() throws Exception {
        Assert.notNull(this.mainClassName, "Unable to find the main class to restart");
        URL[] urls = this.urls.toArray(new URL[0]);
        ClassLoaderFiles updatedFiles = new ClassLoaderFiles(this.classLoaderFiles);
        ClassLoader classLoader = new RestartClassLoader(this.applicationClassLoader, urls, updatedFiles, this.logger);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Starting application " + this.mainClassName + " with URLs " + Arrays.asList(urls));
        }
        return relaunch(classLoader);
    }

protected Throwable relaunch(ClassLoader classLoader) throws Exception {
        RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName, this.args,
                this.exceptionHandler);
//启动RestartLauncher线程
        launcher.start();
        launcher.join();
        return launcher.getError();
    }
}

public class RestartLauncher extends Thread{
@Override
    public void run() {
        try {
            Class mainClass = getContextClassLoader().loadClass(this.mainClassName);
            Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
            mainMethod.invoke(null, new Object[] { this.args });
        }
        catch (Throwable ex) {
            this.error = ex;
            getUncaughtExceptionHandler().uncaughtException(this, ex);
        }
    }
}
  1. 判断文件是不是修改了,关键代码在FileSnapshot
@Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (obj instanceof FileSnapshot) {
            FileSnapshot other = (FileSnapshot) obj;
            boolean equals = this.file.equals(other.file);
            equals = equals && this.exists == other.exists;
            equals = equals && this.length == other.length;
            equals = equals && this.lastModified == other.lastModified;
            return equals;
        }
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        int hashCode = this.file.hashCode();
        hashCode = 31 * hashCode + Boolean.hashCode(this.exists);
        hashCode = 31 * hashCode + Long.hashCode(this.length);
        hashCode = 31 * hashCode + Long.hashCode(this.lastModified);
        return hashCode;
    }

你可能感兴趣的:(怎么样热部署?)