Spring中的Devtools源碼解析
Spring Devtools 核心流程
1.spring.factories
定義了很多需要被初始化的類,程序啟動(dòng)的時(shí)候會(huì)掃描spring.factories并注冊(cè)事件監(jiān)聽者RestartApplicationListener
# Application Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.devtools.restart.RestartScopeInitializer # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.devtools.restart.RestartApplicationListener,\ org.springframework.boot.devtools.logger.DevToolsLogFactory.Listener # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration,\ org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration,\ org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.devtools.env.DevToolsHomePropertiesPostProcessor,\ org.springframework.boot.devtools.env.DevToolsPropertyDefaultsPostProcessor
2. RestartApplicationListener
監(jiān)聽到 ApplicationStartingEvent 事件以后,另外啟動(dòng)一個(gè)線程重新啟動(dòng)main函數(shù)使用 RestartClassLoader 來(lái)加載類,將原來(lái)的主線程hold住
onApplicationStartingEvent(ApplicationStartingEvent event)
- Restarter#initialize
- Restarter#immediateRestart
private void immediateRestart() { try { // 新啟動(dòng)一個(gè)線程執(zhí)行runnable, 執(zhí)行完成以后會(huì)join(),hold住主線程 getLeakSafeThread().callAndWait(() -> { // start-》doStart-》relaunch,RestartLauncher新啟動(dòng)一個(gè)線程執(zhí)行SpringApplication類的main函數(shù) start(FailureHandler.NONE); // 當(dāng)Spring初始化成功以后,先清理相關(guān)緩存 cleanupCaches(); return null; }); } catch (Exception ex) { this.logger.warn("Unable to initialize restarter", ex); } // 這里是在程序結(jié)束以后才會(huì)執(zhí)行到,拋出異常,終止主線程 SilentExitExceptionHandler.exitCurrentThread(); }
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); } } }
3.監(jiān)聽文件變化后進(jìn)行重啟
- ClassPathFileSystemWatcher 在初始化的時(shí)候調(diào)用了fileSystemWatcher.addListener創(chuàng)建了一個(gè)監(jiān)聽者,并且啟動(dòng)了文件夾監(jiān)控線程
public class ClassPathFileSystemWatcher implements InitializingBean, DisposableBean, ApplicationContextAware { ...... @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(); } ...... }
- 文件監(jiān)控 FileSystemWatcher 線程類,就是將要監(jiān)控的目錄加入到this.folders, 啟動(dòng)線程不斷的掃描文件夾的diff, 如果有ChangedFiles,通知監(jiān)聽者fireListeners
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)) { // 得到changeSet,然后fireListeners通知監(jiān)聽者 updateSnapshots(current.values()); } }
- ClassPathFileChangeListener.onChange fireListeners時(shí)被調(diào)用
- publishEvent(new ClassPathChangedEvent(this, changeSet, restart))
- LocalDevToolsAutoConfiguration.RestartConfiguration監(jiān)聽到ClassPathChangedEvent事件然后調(diào)用Restarter.getInstance().restart重啟
- publishEvent(new ClassPathChangedEvent(this, changeSet, restart))
static class RestartConfiguration implements ApplicationListener<ClassPathChangedEvent> { ....... @Override public void onApplicationEvent(ClassPathChangedEvent event) { if (event.isRestartRequired()) { Restarter.getInstance().restart(new FileWatchingFailureHandler(fileSystemWatcherFactory())); } } ...... }
public void restart(FailureHandler failureHandler) { getLeakSafeThread().call(() -> { // 調(diào)用rootContexts.close()銷毀所有的bean,清理緩存,gc Restarter.this.stop(); // 再重啟 Restarter.this.start(failureHandler); return null; }); }
4.RestartClassLoader
優(yōu)先從 updatedFiles 中取更新過(guò)的類進(jìn)行加載 ClassLoaderFiles#getFile
public class RestartClassLoader extends URLClassLoader implements SmartClassLoader { ...... private final ClassLoaderFileRepository updatedFiles; public RestartClassLoader(ClassLoader parent, URL[] urls, ClassLoaderFileRepository updatedFiles, Log logger) { super(urls, parent); this.updatedFiles = updatedFiles; this.logger = logger; } @Override public Enumeration<URL> getResources(String name) throws IOException { // 父類classLoader加載資源 Enumeration<URL> resources = getParent().getResources(name); // 變更類的資源 ClassLoaderFile file = this.updatedFiles.getFile(name); if (file != null) { // Assume that we're replacing just the first item if (resources.hasMoreElements()) { resources.nextElement(); } if (file.getKind() != Kind.DELETED) { // 將更新過(guò)的ClassLoaderFile類放在URL的前面,優(yōu)先加載變更類的URL return new CompoundEnumeration<>(createFileUrl(name, file), resources); } } return resources; } @Override public URL getResource(String name) { // 先加載變更類的URL ClassLoaderFile file = this.updatedFiles.getFile(name); if (file != null && file.getKind() == Kind.DELETED) { return null; } URL resource = findResource(name); if (resource != null) { return resource; } return getParent().getResource(name); } @Override public URL findResource(String name) { final ClassLoaderFile file = this.updatedFiles.getFile(name); if (file == null) { return super.findResource(name); } if (file.getKind() == Kind.DELETED) { return null; } return AccessController.doPrivileged((PrivilegedAction<URL>) () -> createFileUrl(name, file)); } @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); ClassLoaderFile file = this.updatedFiles.getFile(path); if (file != null && file.getKind() == Kind.DELETED) { throw new ClassNotFoundException(name); } synchronized (getClassLoadingLock(name)) { Class<?> loadedClass = findLoadedClass(name); if (loadedClass == null) { try { loadedClass = findClass(name); } catch (ClassNotFoundException ex) { loadedClass = getParent().loadClass(name); } } if (resolve) { resolveClass(loadedClass); } return loadedClass; } } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); final ClassLoaderFile file = this.updatedFiles.getFile(path); if (file == null) { return super.findClass(name); } if (file.getKind() == Kind.DELETED) { throw new ClassNotFoundException(name); } return AccessController.doPrivileged((PrivilegedAction<Class<?>>) () -> { byte[] bytes = file.getContents(); return defineClass(name, bytes, 0, bytes.length); }); } private URL createFileUrl(String name, ClassLoaderFile file) { try { return new URL("reloaded", null, -1, "/" + name, new ClassLoaderFileURLStreamHandler(file)); } catch (MalformedURLException ex) { throw new IllegalStateException(ex); } } ...... }
5.清理緩存
Restarter#cleanupCaches 和其他熱更新類似,清理緩存
private void cleanupCaches() throws Exception { Introspector.flushCaches(); cleanupKnownCaches(); } private void cleanupKnownCaches() throws Exception { ResolvableType.clearCache(); cleanCachedIntrospectionResultsCache(); ReflectionUtils.clearCache(); clearAnnotationUtilsCache(); if (!JavaVersion.getJavaVersion().isEqualOrNewerThan(JavaVersion.NINE)) { clear("com.sun.naming.internal.ResourceManager", "propertiesCache"); } } private void cleanCachedIntrospectionResultsCache() throws Exception { clear(CachedIntrospectionResults.class, "acceptedClassLoaders"); clear(CachedIntrospectionResults.class, "strongClassCache"); clear(CachedIntrospectionResults.class, "softClassCache"); } private void clearAnnotationUtilsCache() throws Exception { try { AnnotationUtils.clearCache(); } catch (Throwable ex) { clear(AnnotationUtils.class, "findAnnotationCache"); clear(AnnotationUtils.class, "annotatedInterfaceCache"); } } private void clear(String className, String fieldName) { try { clear(Class.forName(className), fieldName); } catch (Exception ex) { if (this.logger.isDebugEnabled()) { this.logger.debug("Unable to clear field " + className + " " + fieldName, ex); } } } private void clear(Class<?> type, String fieldName) throws Exception { try { Field field = type.getDeclaredField(fieldName); field.setAccessible(true); Object instance = field.get(null); if (instance instanceof Set) { ((Set<?>) instance).clear(); } if (instance instanceof Map) { ((Map<?, ?>) instance).keySet().removeIf(this::isFromRestartClassLoader); } } catch (Exception ex) { if (this.logger.isDebugEnabled()) { this.logger.debug("Unable to clear field " + type + " " + fieldName, ex); } } }
遠(yuǎn)程更新
服務(wù)端
RemoteDevToolsAutoConfiguration 配置,只有設(shè)置了spring.devtools.remote.secret才初始化
- 增加了GET /接口進(jìn)行健康檢查
- 增加了POST /restart接口進(jìn)行遠(yuǎn)程熱更新,將body體反序列化得到變更的類資源ClassLoaderFiles,然后調(diào)用RestartServer#restart進(jìn)行重啟
- 增加了AccessManager驗(yàn)證接口的請(qǐng)求頭X-AUTH-TOKEN傳遞的secret
@Configuration // 只有設(shè)置了spring.devtools.remote.secret才初始化 @ConditionalOnProperty(prefix = "spring.devtools.remote", name = "secret") public class RemoteDevToolsAutoConfiguration { ...... @Bean @ConditionalOnMissingBean public AccessManager remoteDevToolsAccessManager() { // 權(quán)限空值,根據(jù)請(qǐng)求頭的X-AUTH-TOKEN來(lái)驗(yàn)證密鑰是否一致 RemoteDevToolsProperties remoteProperties = this.properties.getRemote(); return new HttpHeaderAccessManager(remoteProperties.getSecretHeaderName(), remoteProperties.getSecret()); } // 增加一個(gè)根路徑接口GET /來(lái)進(jìn)行監(jiān)控檢查,使用HttpStatusHandler返回一個(gè)HttpStatus.OK來(lái)判定服務(wù)已經(jīng)啟動(dòng)成功 @Bean public HandlerMapper remoteDevToolsHealthCheckHandlerMapper() { Handler handler = new HttpStatusHandler(); Servlet servlet = this.serverProperties.getServlet(); String servletContextPath = (servlet.getContextPath() != null) ? servlet.getContextPath() : ""; return new UrlHandlerMapper(servletContextPath + this.properties.getRemote().getContextPath(), handler); } @Bean @ConditionalOnMissingBean public DispatcherFilter remoteDevToolsDispatcherFilter(AccessManager accessManager, Collection<HandlerMapper> mappers) { // 上面定義HandlerMapper使用AccessManager進(jìn)行攔截權(quán)限 Dispatcher dispatcher = new Dispatcher(accessManager, mappers); return new DispatcherFilter(dispatcher); } @Configuration @ConditionalOnProperty(prefix = "spring.devtools.remote.restart", name = "enabled", matchIfMissing = true) static class RemoteRestartConfiguration { ...... // 增加一個(gè)接口POST /restart進(jìn)行資源更新和重啟, @Bean @ConditionalOnMissingBean(name = "remoteRestartHandlerMapper") public UrlHandlerMapper remoteRestartHandlerMapper(HttpRestartServer server) { Servlet servlet = this.serverProperties.getServlet(); RemoteDevToolsProperties remote = this.properties.getRemote(); String servletContextPath = (servlet.getContextPath() != null) ? servlet.getContextPath() : ""; String url = servletContextPath + remote.getContextPath() + "/restart"; logger.warn("Listening for remote restart updates on " + url); Handler handler = new HttpRestartServerHandler(server); return new UrlHandlerMapper(url, handler); } } }
RestartServer#restart
protected void restart(Set<URL> urls, ClassLoaderFiles files) { Restarter restarter = Restarter.getInstance(); restarter.addUrls(urls); // 優(yōu)先從變更的類中加載類 restarter.addClassLoaderFiles(files); restarter.restart(); }
客戶端
RemoteClientConfiguration配置初始化
- RemoteRestartClientConfiguration
- ClassPathFileSystemWatcher 目錄監(jiān)控初始化
- ClassPathChangeUploader 監(jiān)聽ClassPathChangedEvent事件,調(diào)用遠(yuǎn)程服務(wù)http://remoteUrl/restart接口上傳更新的classLoaderFiles序列化字節(jié)數(shù)據(jù)
- 監(jiān)聽ClassPathChangedEvent事件,當(dāng)文件變更以后,休眠shutdownTime時(shí)間,循環(huán)調(diào)用http://remoteUrl/接口,判斷遠(yuǎn)端服務(wù)是否重啟成功,啟動(dòng)成功以后this.liveReloadServer.triggerReload()自動(dòng)更新瀏覽器
到此這篇關(guān)于Spring中的Devtools源碼解析的文章就介紹到這了,更多相關(guān)Devtools源碼解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA下單接口優(yōu)化實(shí)戰(zhàn)TPS性能提高10倍
今天小編就為大家分享一篇關(guān)于JAVA下單接口優(yōu)化實(shí)戰(zhàn)TPS性能提高10倍,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12解決Properties屬性文件中的值有等號(hào)和換行的小問(wèn)題
這篇文章主要介紹了解決Properties屬性文件中的值有等號(hào)有換行的小問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08SpringValidation數(shù)據(jù)校驗(yàn)之約束注解與分組校驗(yàn)方式
本文將深入探討Spring Validation的核心功能,幫助開發(fā)者掌握約束注解的使用技巧和分組校驗(yàn)的高級(jí)應(yīng)用,從而構(gòu)建更加健壯和可維護(hù)的Java應(yīng)用程序,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04在Java SE上使用Headless模式的超級(jí)指南
這篇文章主要介紹了在Java SE上使用Headless模式的超級(jí)指南,文中介紹了Headless模式實(shí)際使用的各種技巧,極力推薦!需要的朋友可以參考下2015-07-07