什么叫热部署?
jvm已经启动,修改了代码之后,不用重启jvm,编译代码之后重新加载一个新的class文件,直接生效
一般在线上关闭,在开发调试的时候打开热部署;因为热部署检测class文件修改,自己加载,不可控,有可能出现不可知的问题
实现热部署有两个关键: 一是发现文件修改了,二是打破两亲委派模式,自定义加载自己的类
- 加载spring-boot-devtools/META-INF/spring.factories中,LocalDevtoolsAutoConfiguration类
- 注入了一个类FileSystemWatcherFactory,文件修改监听器工厂;一个文件修改监听器
@Bean
FileSystemWatcherFactory fileSystemWatcherFactory() {
return this::newFileSystemWatcher;
}
private FileSystemWatcher newFileSystemWatcher() {
Restart restartProperties = this.properties.getRestart();
FileSystemWatcher watcher = new FileSystemWatcher(true, restartProperties.getPollInterval(),
restartProperties.getQuietPeriod());
String triggerFile = restartProperties.getTriggerFile();
if (StringUtils.hasLength(triggerFile)) {
watcher.setTriggerFilter(new TriggerFileFilter(triggerFile));
}
List<File> additionalPaths = restartProperties.getAdditionalPaths();
for (File path : additionalPaths) {
watcher.addSourceFolder(path.getAbsoluteFile());
}
return watcher;
}
@Bean
@ConditionalOnMissingBean
ClassPathFileSystemWatcher classPathFileSystemWatcher(FileSystemWatcherFactory fileSystemWatcherFactory,
ClassPathRestartStrategy classPathRestartStrategy) {
URL[] urls = Restarter.getInstance().getInitialUrls();
ClassPathFileSystemWatcher watcher = new ClassPathFileSystemWatcher(fileSystemWatcherFactory,
classPathRestartStrategy, urls);
watcher.setStopWatcherOnRestart(true);
return watcher;
}
//在容器实例化完成之后,执行bean生命周期方法
public class ClassPathFileSystemWatcher implements InitializingBean, DisposableBean, ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@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();
}
@Override
public void destroy() throws Exception {
this.fileSystemWatcher.stop();
}
}
public class FileSystemWatcher {
public void start() {
synchronized (this.monitor) {
saveInitialSnapshots();
if (this.watchThread == null) {
Map<File, FolderSnapshot> localFolders = new HashMap<>(this.folders);
this.watchThread = new Thread(new Watcher(this.remainingScans, new ArrayList<>(this.listeners),
this.triggerFilter, this.pollInterval, this.quietPeriod, localFolders));
this.watchThread.setName("File Watcher");
this.watchThread.setDaemon(this.daemon);
// -------------------------这里启动了Watcher线程,准备执行run()方法------------------
this.watchThread.start();
}
}
}
@Override
public void run() {
int remainingScans = this.remainingScans.get();
while (remainingScans > 0 || remainingScans == -1) {
try {
if (remainingScans > 0) {
this.remainingScans.decrementAndGet();
}
scan();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
remainingScans = this.remainingScans.get();
}
}
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());
}
}
private boolean isDifferent(Map<File, FolderSnapshot> previous, Map<File, FolderSnapshot> current) {
if (!previous.keySet().equals(current.keySet())) {
return true;
}
for (Map.Entry<File, FolderSnapshot> entry : previous.entrySet()) {
FolderSnapshot previousFolder = entry.getValue();
FolderSnapshot currentFolder = current.get(entry.getKey());
if (!previousFolder.equals(currentFolder, this.triggerFilter)) {
return true;
}
}
return false;
}
private Map<File, FolderSnapshot> getCurrentSnapshots() {
Map<File, FolderSnapshot> snapshots = new LinkedHashMap<>();
for (File folder : this.folders.keySet()) {
snapshots.put(folder, new FolderSnapshot(folder));
}
return snapshots;
}
private void updateSnapshots(Collection<FolderSnapshot> snapshots) {
Map<File, FolderSnapshot> updated = new LinkedHashMap<>();
Set<ChangedFiles> changeSet = new LinkedHashSet<>();
for (FolderSnapshot snapshot : snapshots) {
FolderSnapshot previous = this.folders.get(snapshot.getFolder());
updated.put(snapshot.getFolder(), snapshot);
ChangedFiles changedFiles = previous.getChangedFiles(snapshot, this.triggerFilter);
if (!changedFiles.getFiles().isEmpty()) {
changeSet.add(changedFiles);
}
}
if (!changeSet.isEmpty()) {
//发布文件修改事件
fireListeners(Collections.unmodifiableSet(changeSet));
}
this.folders = updated;
}
private void fireListeners(Set<ChangedFiles> changeSet) {
for (FileChangeListener listener : this.listeners) {
//---------这里调用 ClassPathFileChangeListener#onchange()------
listener.onChange(changeSet);
}
}
}
class ClassPathFileChangeListener{
@Override
public void onChange(Set<ChangedFiles> changeSet) {
boolean restart = isRestartRequired(changeSet);
publishEvent(new ClassPathChangedEvent(this, changeSet, restart));
}
private void publishEvent(ClassPathChangedEvent event) {
this.eventPublisher.publishEvent(event);
if (event.isRestartRequired() && this.fileSystemWatcherToStop != null) {
this.fileSystemWatcherToStop.stop();
}
}}
- 接收文件修改事件Event的处理类
static class LiveReloadServerEventListener {
private final OptionalLiveReloadServer liveReloadServer;
LiveReloadServerEventListener(OptionalLiveReloadServer liveReloadServer) {
this.liveReloadServer = liveReloadServer;
}
@EventListener
public void onContextRefreshed(ContextRefreshedEvent event) {
this.liveReloadServer.triggerReload();
}
//监听到了文件修改事件
@EventListener
public void onClassPathChanged(ClassPathChangedEvent event) {
if (!event.isRestartRequired()) {
this.liveReloadServer.triggerReload();
}
}
}
//源码是这个
@Bean
ApplicationListener<ClassPathChangedEvent> restartingClassPathChangedEventListener(
FileSystemWatcherFactory fileSystemWatcherFactory) {
return (event) -> {
if (event.isRestartRequired()) {
//重启容器
Restarter.getInstance().restart(new FileWatchingFailureHandler(fileSystemWatcherFactory));
}
};
}
public class Restarter{
public void restart(FailureHandler failureHandler) {
if (!this.enabled) {
this.logger.debug("Application restart is disabled");
return;
}
this.logger.debug("Restarting application");
getLeakSafeThread().call(() -> {
//先停止,调用ApplicationContext.stop()
Restarter.this.stop();
//再启动,通过反射把Main方法再执行一次
Restarter.this.start(failureHandler);
return null;
});
}
protected void stop() throws Exception {
this.logger.debug("Stopping application");
this.stopLock.lock();
try {
for (ConfigurableApplicationContext context : this.rootContexts) {
context.close();
this.rootContexts.remove(context);
}
cleanupCaches();
if (this.forceReferenceCleanup) {
forceReferenceCleanup();
}
}
finally {
this.stopLock.unlock();
}
//通知jvm可以gc
System.gc();
System.runFinalization();
}
protected void start(FailureHandler failureHandler) throws Exception {
do {
//开始启动
Throwable error = doStart();
if (error == null) {
return;
}
if (failureHandler.handle(error) == Outcome.ABORT) {
return;
}
}
while (true);
}
private Throwable doStart() throws Exception {
Assert.notNull(this.mainClassName, "Unable to find the main class to restart");
URL[] urls = this.urls.toArray(new URL[0]);
ClassLoaderFiles updatedFiles = new ClassLoaderFiles(this.classLoaderFiles);
ClassLoader classLoader = new RestartClassLoader(this.applicationClassLoader, urls, updatedFiles, this.logger);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Starting application " + this.mainClassName + " with URLs " + Arrays.asList(urls));
}
return relaunch(classLoader);
}
protected Throwable relaunch(ClassLoader classLoader) throws Exception {
RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName, this.args,
this.exceptionHandler);
//启动RestartLauncher线程
launcher.start();
launcher.join();
return launcher.getError();
}
}
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);
}
}
}
- 判断文件是不是修改了,关键代码在FileSnapshot
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof FileSnapshot) {
FileSnapshot other = (FileSnapshot) obj;
boolean equals = this.file.equals(other.file);
equals = equals && this.exists == other.exists;
equals = equals && this.length == other.length;
equals = equals && this.lastModified == other.lastModified;
return equals;
}
return super.equals(obj);
}
@Override
public int hashCode() {
int hashCode = this.file.hashCode();
hashCode = 31 * hashCode + Boolean.hashCode(this.exists);
hashCode = 31 * hashCode + Long.hashCode(this.length);
hashCode = 31 * hashCode + Long.hashCode(this.lastModified);
return hashCode;
}
网友评论