大体流程
监听器注册,在发布starting事件时,捕获这个事件,替换classloader
细节
RestartApplicationListener.onApplicationStartingEvent
-
创建默认重启初始化器,其中主要维护了classloader加载class文件的路径
// 有没有设置系统变量,设置了就创建默认的,没有设置初始化器为开发模式 String enabled = System.getProperty(ENABLED_PROPERTY); RestartInitializer restartInitializer = null; if (enabled == null) { restartInitializer = new DefaultRestartInitializer(); } else if (Boolean.parseBoolean(enabled)) { restartInitializer = new DefaultRestartInitializer() { // 开发模式classloader @Override protected boolean isDevelopmentClassLoader(ClassLoader classLoader) { return true; } }; } -
有没有使用热重启agent,如果没有,就标记为初始化时重启项目,否则反之,该步骤不做关心
主要关心Restarter.initialize(args, false, restartInitializer, restartOnInitialize);
// 有没有创建成功初始化器,创建成功后,判断是否有使用热部署插件,如果有就标记在初始化时重启项目 if (restartInitializer != null) { String[] args = event.getArgs(); // 判断是否有使用热部署初始化器,如果没有,就标记为在初始化时重启项目 boolean restartOnInitialize = !AgentReloader.isActive(); // 重启器初始化 Restarter.initialize(args, false, restartInitializer, restartOnInitialize); } else { // 若没有初始化成功初始化器,则禁用重启器 Restarter.disable(); } -
Restarter.initialize干了什么?
参数:args(String[]):main方法的入参;forceReferenceCleanup(boolean):是否强制清除参数;initializer(RestartInitializer):重启初始化器;restartOnInitialize(boolean):是否在重启时初始化
- 线程锁住保证只会创建一个单例重启器保存下来‘
Restarter localInstance = null; synchronized (INSTANCE_MONITOR) { if (instance == null) { // 创建重启器,关注这步骤 localInstance = new Restarter(Thread.currentThread(), args, forceReferenceCleanup, initializer); // 赋值单例重启器 instance = localInstance; } }-
new Restarter干了什么?
参数:当前线程,main方法的入参,是否强制清除参数,重启初始化器
// 创建静默退出控制器,线程相关知识,由于主线程不能直接获取子线程的线程信息,但是每个线程可以通过setUncaughtExceptionHandler 注册一个回调接口 // 这里就是创建了一个静默退出的回调器,赋值给了当前线程,当前线程抛出异常后,就能被主线程捕捉到,既然是禁摩退出,所以会在替换完成classloader之 // 后该线程抛出异常,实现线程切换 SilentExitExceptionHandler.setup(thread); //强制清除参数标记 this.forceReferenceCleanup = forceReferenceCleanup; // 初始化路径,从初始化器中获取 this.initialUrls = initializer.getInitialUrls(thread); // 获取该线程的入口方法,用于重启 this.mainClassName = getMainClassName(thread); // 获取该线程的类加载器——appClassLoader this.applicationClassLoader = thread.getContextClassLoader(); // 主线程参数 this.args = args; // 线程的回调 this.exceptionHandler = thread.getUncaughtExceptionHandler(); // 穿件一个泄露安全线程,放到list中,暂时不知道什么用 this.leakSafeThreads.add(new LeakSafeThread());
-
- 重启器初始化
if (localInstance != null) { // 初始化重启器 localInstance.initialize(restartOnInitialize); } - localInstance.initialize干了什么?
- 提前加载一些类到内存,确保它们不会拿到后续被替换的RestartLoader
- 设置上所有class的加载路径
- 如果restartOnInitialize(初始化时重启标记)为true,则立即重启(调用immediateRestart())
// a preInitializeLeakyClasses(); if (this.initialUrls != null) { // b this.urls.addAll(Arrays.asList(this.initialUrls)); if (restartOnInitialize) { // c immediateRestart(); } }
- immediateRestart干了什么?
- 拿到4.i.a中设置的泄露安全线程的第一个(队列)
- 设置回调,启动并暂停主线程线程,让这个线程先完成再说 start -> join
- 在泄露安全线程队列中继续追加一个新的线程对象
- 执行回调,回调就是启动方法,若启动失败,则异常退出,启动成功,清除缓存
getLeakSafeThread().callAndWait(() -> {// 设置回调 start(FailureHandler.NONE); cleanupCaches(); return null; }); // 重启完毕,该线程静默退出,注意上面一步虽然开启了线程去重启,但是使用了join是操作串行青睐了,所以 // 只有当上面一步执行完毕后,才会退出该线程 SilentExitExceptionHandler.exitCurrentThread(); ===============================================>>> getLeakSafeThread() try { // a. 拿到4.i.a中设置的泄露安全线程的第一个(队列) return this.leakSafeThreads.takeFirst(); } catch (InterruptedException ex) { // 中断此线程 Thread.currentThread().interrupt(); throw new IllegalStateException(ex); } ===============================================>>> callAndWait() // 设置回调 this.callable = callable; // 启动这个线程 start(); try { // 强制先执行该线程的run返回 join(); return (V) this.result; } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new IllegalStateException(ex); } ===============================================>>> run() try { // 之前取出来一个,这里再塞回去一个 Restarter.this.leakSafeThreads.put(new LeakSafeThread()); // 执行回调 this.result = this.callable.call(); } catch (Exception ex) { ex.printStackTrace(); // 回调报错,异常退出 System.exit(1); } ===============================================>>> this.callable.call() // Restarter重启器的重启方法,传入一个流产方式,当start失败报错时流产,啥也不做直接return start(FailureHandler.NONE); // 无论是流产退出还是成功退出,才会向下执行清理缓存 cleanupCaches(); return null;
- Restarter.start干了什么?空壳方法,主要逻辑在doStart,doStart成功,或者被标记为失败就流产时(FailureHandler.NONE)才会退出。
do { // 执行启动方法 Throwable error = doStart(); // 启动成功,退出循环 if (error == null) { return; } // 启动不成功,但是标记为流产,退出循环 if (failureHandler.handle(error) == Outcome.ABORT) { return; } } while (true); - Restarter.doStart做了什么
- 获取class文件加载路径
- 获取更新文件
- 创建新的RestartClassLoader,将applicationClassLoader设置为他的爸爸,并将class文件加载路径和更新文件、日志记录器赋值给他
- 重新启动并返回(relaunch)
// a URL[] urls = this.urls.toArray(new URL[0]); // b ClassLoaderFiles updatedFiles = new ClassLoaderFiles(this.classLoaderFiles); // c ClassLoader classLoader = new RestartClassLoader(this.applicationClassLoader, urls, updatedFiles, this.logger); // d return relaunch(classLoader);
- Restarter.relaunch做了什么?
-
创建重启器(线程)
- 设置入口方法所在全限定类名,和参数
- 设置线程名字
- 设置报错捕获器
- 标记该线程非守护线程(死了就死了,守护线程等所有非守护线程死了,他才能死)
- 设置上下文加载器为restartClassLoad(完成对classLoader的替换),如果系统有安全管理机制,先检查是否可以通过setContextClassLoader设置上下文类加载器。
this.mainClassName = mainClassName; this.args = args; setName("restartedMain"); setUncaughtExceptionHandler(exceptionHandler); setDaemon(false); setContextClassLoader(classLoader);
-
启动重启器,重启器强行加入线程 start -> join
- run干了什么(注意这个run跑完了才会切主线程)
try { // 通过vii.a.a 设置的入口类和restartClassLoad获取入口类对象 Class<?> mainClass = Class.forName(this.mainClassName, false, getContextClassLoader()); // 通过入口类对象获取main方法 Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); // 执行main方法 mainMethod.invoke(null, new Object[] { this.args }); } catch (Throwable ex) { // 这步先返回到 vii 再返回到 v 中通过doStart执行结果返回,如果没有异常this.error=null; this.error = ex; // 报错就捕获这个异常,通过异常捕获器将其抛出到主线程 getUncaughtExceptionHandler().uncaughtException(this, ex); }
- run干了什么(注意这个run跑完了才会切主线程)
-
返回重启器执行结果(是否报错)
// a RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName, this.args, this.exceptionHandler); // b launcher.start(); launcher.join(); // c return launcher.getError();
-
- 线程锁住保证只会创建一个单例重启器保存下来‘