最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring中的Devtools源碼解析

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

Spring Devtools 核心流程

1.spring.factories

定義了很多需要被初始化的類,程序啟動的時候會掃描spring.factories并注冊事件監(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 事件以后,另外啟動一個線程重新啟動main函數(shù)使用 RestartClassLoader 來加載類,將原來的主線程hold住

onApplicationStartingEvent(ApplicationStartingEvent event)

  • Restarter#initialize
    • Restarter#immediateRestart
private void immediateRestart() {
	try {
	    // 新啟動一個線程執(zhí)行runnable, 執(zhí)行完成以后會join(),hold住主線程
		getLeakSafeThread().callAndWait(() -> {
		    // start-》doStart-》relaunch,RestartLauncher新啟動一個線程執(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é)束以后才會執(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 在初始化的時候調(diào)用了fileSystemWatcher.addListener創(chuàng)建了一個監(jiān)聽者,并且啟動了文件夾監(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, 啟動線程不斷的掃描文件夾的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時被調(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 中取更新過的類進(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) {
			    // 將更新過的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驗證接口的請求頭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ù)請求頭的X-AUTH-TOKEN來驗證密鑰是否一致
		RemoteDevToolsProperties remoteProperties = this.properties.getRemote();
		return new HttpHeaderAccessManager(remoteProperties.getSecretHeaderName(), remoteProperties.getSecret());
	}
    // 增加一個根路徑接口GET /來進(jìn)行監(jiān)控檢查,使用HttpStatusHandler返回一個HttpStatus.OK來判定服務(wù)已經(jī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 {
	    ......
	    // 增加一個接口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時間,循環(huán)調(diào)用http://remoteUrl/接口,判斷遠(yuǎn)端服務(wù)是否重啟成功,啟動成功以后this.liveReloadServer.triggerReload()自動更新瀏覽器

到此這篇關(guān)于Spring中的Devtools源碼解析的文章就介紹到這了,更多相關(guān)Devtools源碼解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java多線程Thread類的使用及注意事項

    Java多線程Thread類的使用及注意事項

    這篇文章主要介紹了Java多線程Thread類的使用及注意事項,在java標(biāo)準(zhǔn)庫中提供了一個Thread類來表示/操作線程,Thread類也可以視為是java標(biāo)準(zhǔn)庫提供的API
    2022-06-06
  • Java中的StringBuilder類解析

    Java中的StringBuilder類解析

    這篇文章主要介紹了Java中的StringBuilder類解析,該類被設(shè)計用作StringBuffer的一個簡易替換,用在字符串緩沖區(qū)被單線程使用的時候,如果可能,優(yōu)先采用該類,因為在大多數(shù)實現(xiàn)中,String Builder比StringBuffer要快,需要的朋友可以參考下
    2023-09-09
  • 三步輕松搭建springMVC框架

    三步輕松搭建springMVC框架

    這篇文章主要教大家三步輕松搭建springMVC框架,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • java貪吃蛇游戲編寫代碼

    java貪吃蛇游戲編寫代碼

    這篇文章主要為大家詳細(xì)介紹了java貪吃蛇游戲的編寫代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • java中的return關(guān)鍵字使用解讀

    java中的return關(guān)鍵字使用解讀

    這篇文章主要介紹了java中的return關(guān)鍵字使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • 如何在Java中實現(xiàn)一個散列表

    如何在Java中實現(xiàn)一個散列表

    這篇文章主要介紹了如何在Java中實現(xiàn)一個散列表,建一個HashMap,以String類型為Key,Int類型為Value,下文具體的操作過程需要的小伙伴可以參考一下
    2022-04-04
  • springboot創(chuàng)建文件夾失敗的解決

    springboot創(chuàng)建文件夾失敗的解決

    這篇文章主要介紹了springboot創(chuàng)建文件夾失敗的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java實現(xiàn)添加,讀取和刪除Excel圖片的方法詳解

    Java實現(xiàn)添加,讀取和刪除Excel圖片的方法詳解

    本文介紹在Java程序中如何添加圖片到excel表格,以及如何讀取、刪除excel表格中已有的圖片。文中的示例代碼講解詳細(xì),感興趣的可以學(xué)習(xí)一下
    2022-05-05
  • Eclipse中maven的配置詳解

    Eclipse中maven的配置詳解

    這篇文章主要介紹了Eclipse中maven的配置詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Java高并發(fā)編程之CAS實現(xiàn)無鎖隊列代碼實例

    Java高并發(fā)編程之CAS實現(xiàn)無鎖隊列代碼實例

    這篇文章主要介紹了Java高并發(fā)編程之CAS實現(xiàn)無鎖隊列代碼實例,在多線程操作中,我們通常會添加鎖來保證線程的安全,那么這樣勢必會影響程序的性能,那么為了解決這一問題,于是就有了在無鎖操作的情況下依然能夠保證線程的安全,需要的朋友可以參考下
    2023-12-12

最新評論

香河县| 嘉义县| 东平县| 进贤县| 含山县| 庄河市| 屏东县| 永清县| 开原市| 朔州市| 宁陵县| 平定县| 敦化市| 太仓市| 达拉特旗| 长乐市| 临沭县| 柏乡县| 东阳市| 余干县| 德昌县| 上思县| 泽库县| 寿宁县| 云浮市| 康马县| 阳西县| 元阳县| 冕宁县| 响水县| 林甸县| 沈阳市| 南昌市| 兴业县| 左权县| 高邮市| 广汉市| 兴和县| 平顶山市| 自贡市| 平谷区|