看别人的源码解析自己不上手的话其实并没有真正掌握源码 官方使用说明 使用文档中文翻译 源码在Spring boot项目中 Spring Devtools 源码初步解析
核心流程
1.spring.factories
定义了很多需要被初始化的类,程序启动的时候会扫描spring.factories并注册事件监听者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
监听到ApplicationStartingEvent 事件以后,另外启动一个线程重新启动main函数使用RestartClassLoader 来加载类,将原来的主线程hold住
onApplicationStartingEvent(ApplicationStartingEvent event)
Restarter#initialize
Restarter#immediateRestart private void immediateRestart() {
try {
getLeakSafeThread().callAndWait(() -> {
start(FailureHandler.NONE);
cleanupCaches();
return null;
});
}
catch (Exception ex) {
this.logger.warn("Unable to initialize restarter", ex);
}
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.监听文件变化后进行重启
ClassPathFileSystemWatcher 在初始化的时候调用了fileSystemWatcher.addListener创建了一个监听者,并且启动了文件夹监控线程
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();
}
......
}
- 文件监控
FileSystemWatcher 线程类,就是将要监控的目录加入到this.folders, 启动线程不断的扫描文件夹的diff, 如果有ChangedFiles,通知监听者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)) {
updateSnapshots(current.values());
}
}
ClassPathFileChangeListener.onChange fireListeners时被调用
publishEvent(new ClassPathChangedEvent(this, changeSet, restart))
LocalDevToolsAutoConfiguration.RestartConfiguration 监听到ClassPathChangedEvent 事件然后调用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(() -> {
Restarter.this.stop();
Restarter.this.start(failureHandler);
return null;
});
}
4.RestartClassLoader
优先从updatedFiles 中取更新过的类进行加载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 {
Enumeration<URL> resources = getParent().getResources(name);
ClassLoaderFile file = this.updatedFiles.getFile(name);
if (file != null) {
if (resources.hasMoreElements()) {
resources.nextElement();
}
if (file.getKind() != Kind.DELETED) {
return new CompoundEnumeration<>(createFileUrl(name, file), resources);
}
}
return resources;
}
@Override
public URL getResource(String name) {
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);
}
}
}
远程更新
服务端
RemoteDevToolsAutoConfiguration 配置,只有设置了spring.devtools.remote.secret才初始化
- 增加了
GET / 接口进行健康检查 - 增加了
POST /restart 接口进行远程热更新,将body体反序列化得到变更的类资源ClassLoaderFiles,然后调用RestartServer#restart 进行重启 - 增加了
AccessManager 验证接口的请求头X-AUTH-TOKEN传递的secret
@Configuration
@ConditionalOnProperty(prefix = "spring.devtools.remote", name = "secret")
public class RemoteDevToolsAutoConfiguration {
......
@Bean
@ConditionalOnMissingBean
public AccessManager remoteDevToolsAccessManager() {
RemoteDevToolsProperties remoteProperties = this.properties.getRemote();
return new HttpHeaderAccessManager(remoteProperties.getSecretHeaderName(), remoteProperties.getSecret());
}
@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) {
Dispatcher dispatcher = new Dispatcher(accessManager, mappers);
return new DispatcherFilter(dispatcher);
}
@Configuration
@ConditionalOnProperty(prefix = "spring.devtools.remote.restart", name = "enabled", matchIfMissing = true)
static class RemoteRestartConfiguration {
......
@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);
restarter.addClassLoaderFiles(files);
restarter.restart();
}
客户端
RemoteClientConfiguration 配置初始化
RemoteRestartClientConfiguration
ClassPathFileSystemWatcher 目录监控初始化ClassPathChangeUploader 监听ClassPathChangedEvent 事件,调用远程服务http://remoteUrl/restart 接口上传更新的classLoaderFiles 序列化字节数据 - 监听
ClassPathChangedEvent 事件,当文件变更以后,休眠shutdownTime 时间,循环调用http://remoteUrl/ 接口,判断远端服务是否重启成功,启动成功以后this.liveReloadServer.triggerReload() 自动更新浏览器
|