欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring中的Devtools源碼解析

 更新時(shí)間:2023年10月12日 10:01:04   作者:項(xiàng)哥  
這篇文章主要介紹了Spring中的Devtools源碼解析,Spring中的Devtools是一個(gè)開發(fā)工具,旨在提高開發(fā)人員的生產(chǎn)力和開發(fā)體驗(yàn),它提供了一系列功能,包括自動(dòng)重啟、熱部署、遠(yuǎn)程調(diào)試等,使開發(fā)人員能夠更快速地進(jìn)行代碼修改和調(diào)試,需要的朋友可以參考下

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重啟
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)文章

  • Spring中DeferredResult異步處理

    Spring中DeferredResult異步處理

    DeferredResult是Spring中處理異步請(qǐng)求的強(qiáng)大工具,可以幫助改善應(yīng)用程序的性能和用戶體驗(yàn),本文就來(lái)介紹一下Spring中DeferredResult異步處理,感興趣的可以了解一下
    2023-12-12
  • Java通過(guò)URL類下載圖片的實(shí)例代碼

    Java通過(guò)URL類下載圖片的實(shí)例代碼

    這篇文章主要介紹了Java通過(guò)URL類下載圖片,文中結(jié)合實(shí)例代碼補(bǔ)充介紹了java通過(guò)url獲取圖片文件的相關(guān)知識(shí),代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • java的四種常用輸入方法你會(huì)幾種

    java的四種常用輸入方法你會(huì)幾種

    這篇文章主要介紹了java四種常用輸入方法的相關(guān)資料,分別是Scanner、System、命令行和JOptionPane,每種方法都有其特點(diǎn)和適用場(chǎng)景,文中提供了詳細(xì)的代碼示例,需要的朋友可以參考下
    2025-03-03
  • Java進(jìn)程間通信之消息隊(duì)列

    Java進(jìn)程間通信之消息隊(duì)列

    這篇文章主要為大家詳細(xì)介紹了Java進(jìn)程間通信之消息隊(duì)列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03
  • JAVA下單接口優(yōu)化實(shí)戰(zhàn)TPS性能提高10倍

    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)題

    這篇文章主要介紹了解決Properties屬性文件中的值有等號(hào)有換行的小問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • IDEA中查看類繼承圖和類源碼的騷操作

    IDEA中查看類繼承圖和類源碼的騷操作

    這篇文章主要介紹了IDEA中查看類繼承圖和類源碼的騷操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • SpringValidation數(shù)據(jù)校驗(yàn)之約束注解與分組校驗(yàn)方式

    SpringValidation數(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
  • springboot整合mqtt的詳細(xì)圖文教程

    springboot整合mqtt的詳細(xì)圖文教程

    MQTT是一種基于發(fā)布/訂閱(publish/subscribe)模式的“輕量級(jí)”通訊協(xié)議,該協(xié)議構(gòu)建于TCP/IP協(xié)議上,由IBM在1999年發(fā)布,下面這篇文章主要給大家介紹了關(guān)于springboot整合mqtt的詳細(xì)圖文教程,需要的朋友可以參考下
    2023-02-02
  • 在Java SE上使用Headless模式的超級(jí)指南

    在Java SE上使用Headless模式的超級(jí)指南

    這篇文章主要介紹了在Java SE上使用Headless模式的超級(jí)指南,文中介紹了Headless模式實(shí)際使用的各種技巧,極力推薦!需要的朋友可以參考下
    2015-07-07

最新評(píng)論