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

