美文网首页
怎么样热部署?

怎么样热部署?

作者: Depro | 来源:发表于2021-08-10 08:24 被阅读0次

    什么叫热部署?

    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<File> 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<File, FolderSnapshot> 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<File, FolderSnapshot> previous;
                Map<File, FolderSnapshot> 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<File, FolderSnapshot> previous, Map<File, FolderSnapshot> current) {
                if (!previous.keySet().equals(current.keySet())) {
                    return true;
                }
                for (Map.Entry<File, FolderSnapshot> 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<File, FolderSnapshot> getCurrentSnapshots() {
                Map<File, FolderSnapshot> snapshots = new LinkedHashMap<>();
                for (File folder : this.folders.keySet()) {
                    snapshots.put(folder, new FolderSnapshot(folder));
                }
                return snapshots;
            }
    
            private void updateSnapshots(Collection<FolderSnapshot> snapshots) {
                Map<File, FolderSnapshot> updated = new LinkedHashMap<>();
                Set<ChangedFiles> 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<ChangedFiles> changeSet) {
                for (FileChangeListener listener : this.listeners) {
     //---------这里调用 ClassPathFileChangeListener#onchange()------
                    listener.onChange(changeSet);
                }
            }
    }
    
    
    class ClassPathFileChangeListener{
    
           @Override
        public void onChange(Set<ChangedFiles> 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<ClassPathChangedEvent> 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;
        }
    

    相关文章

      网友评论

          本文标题:怎么样热部署?

          本文链接:https://www.haomeiwen.com/subject/azdjvltx.html