spring boot源码分析之启动与热部署

原创
2021/07/03 12:53
阅读数 1.9K

一个简单的springboot启动类:

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

我们在使用时,只需要执行main方法即可启动一个应用。本篇博客首先分析这个类的运行过程。

 

首先看下@SpringBootApplication做了什么:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

可见该注解主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan组成。

@SpringBootConfiguration:

@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

这个注解上面有一个@Configuration注解,即此注解相当于一个@Configuration注解,此处包了一层应该是提供给springboot启动类使用,用于区分。

 

@ComponentScan注解:

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

属于Spring的注解,用于指定扫描包路径。这里指定了excludeFilters属性为TypeExcludeFilter,我们使用的时候只需要继承TypeExcludeFilter并重写match方法即可自定义排除逻辑。

 

接下来是核心逻辑的@EnableAutoConfiguration注解,它实现了springboot的自动装配功能,可以看到这里Import了一个AutoConfigurationImportSelector:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

首先简单看下@AutoConfigurationPackage的逻辑:

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
}

它Import了一个AutoConfigurationPackages.Registrar,它实现了ImportBeanDefinitionRegistrar,主要逻辑是将当前配置类所在包保存在BasePackages中,并向容器中注册这个BasePackages的BeanDefinition,供Spring内部使用。

 

AutoConfigurationImportSelector:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}

它实现了DeferredImportSelectorSpring接口

重写了getImportGroup方法,使返回一个AutoConfigurationGroup用于分组(和容器中其它Bean区分开来,不会互相影响)

	public Class<? extends Group> getImportGroup() {
		return AutoConfigurationGroup.class;
	}

AutoConfigurationGroup重写了process方法,那么它会在容器处理完Configuration Bean的最后运行,调用这里的getAutoConfigurationEntry方法。

		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

		public Iterable<Entry> selectImports() {
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			processedConfigurations.removeAll(allExclusions);

			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

这里将自动装配的类记录到autoConfigurationEntries上,后续run方法里面会执行selectImports()方法,从autoConfigurationEntries中取类进行排序(因为分组了所以只对组内Bean排序),加载类到容器中。

getAutoConfigurationEntry:

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
        // 解析注解,获得exclude和excludeName属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // SPI加载spring.factories文件中配置的类,此处加载的是EnableAutoConfiguration对应的类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 使用LinkedHashSet去重
		configurations = removeDuplicates(configurations);
        // 获得注解上配置的需要排除的类名,放入Set中
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        // 校验无效的排除,指定不存在的类且不存在于configurations中视为无效的排除,会抛出IllegalStateException
		checkExcludedClasses(configurations, exclusions);
        // 排除类
		configurations.removeAll(exclusions);
        // SPI加载AutoConfigurationImportFilter对应的类(OnBeanCondition,OnClassCondition,OnWebApplicationCondition)
        // 会对他们进行过滤
		configurations = getConfigurationClassFilter().filter(configurations);
        // SPI加载AutoConfigurationImportListener对应的Listener,调用其onAutoConfigurationImportEvent方法
        // 可以实现AutoConfigurationImportListener接口对这里的候选类和排除类列表进行扩展
		fireAutoConfigurationImportEvents(configurations, exclusions);
        // 返回一个AutoConfigurationEntry,这个对象就是把候选类和排除类列表进行记录
		return new AutoConfigurationEntry(configurations, exclusions);
	}

-------------------------------------------------------------------------------------------------------

Spring Condition说明:

HttpEncodingAutoConfiguration:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {}

@ConditionalOnWebApplication:当前是Web环境则HttpEncodingAutoConfiguration生效

@ConditionalOnClass:此处如果容器中有CharacterEncodingFilter则生效

@ConditionalOnProperty:此处检查是否配置了server.servlet.encoding.enabled,没有配置默认为true

-------------------------------------------------------------------------------------------------------

 

接下来分析SpringApplication.run(Application.class, args)这句代码做了什么事:

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

可以看到里面是new了一个SpringApplication调用其run方法。

	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 指定资源加载策略,此处为空
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
        // 记录当前传入的启动类class
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 推断出web应用环境(响应式,非web应用,servlet环境)
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // SPI加载ApplicationContextInitializer一些默认类,后续run方法中会去调用他们的initialize方法
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // SPI加载一些默认的监听器ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 推断出应用启动类的class
		this.mainApplicationClass = deduceMainApplicationClass();
	}

可以看到创建SpringApplication的过程比较简单,就是加载了一些配置。

接下来是run的逻辑:

	public ConfigurableApplicationContext run(String... args) {
        // 计数工具类,此处用于记录应用启动耗时
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 配置Headless模式,默认为true(在该模式下,系统缺少了显示设备、键盘或鼠标。)
		configureHeadlessProperty();
        // SPI加载SpringApplicationRunListener运行方法的监听器
        // 加载SpringApplicationRunListener事件,我们可以实现它在应用启动开始时做一些事情
        // 但是自定义类要写入spring.factories中
		SpringApplicationRunListeners listeners = getRunListeners(args);
        // 这里会依次调用它们的starting方法
		listeners.starting();
		try {
            // args是我们启动时传入的参数,这里封装为一个DefaultApplicationArguments对象
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 根据监听器listeners去读取环境和配置相关信息
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            // 获得spring.beaninfo.ignore配置信息,未配置则这里置为true
            // 那么spring会忽略BeanInfo,它是jdk自带的描述一个对象的Bean对象
            // 我们可以通过Introspector.getBeanInfo方法来使用它
			configureIgnoreBeanInfo(environment);
            // 打印logo的逻辑,在里面可以看到默认是取的banner.txt这个文件内容
			Banner printedBanner = printBanner(environment);
            // 根据Web应用类型反射创建ApplicationContext容器
            // servlet则创建AnnotationConfigServletWebServerApplicationContext
			context = createApplicationContext();
            // SPI加载SpringBootExceptionReporter并排序
            // SpringBootExceptionReporter作用就是打印异常日志
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            // 准备环境
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 首先发布一个JVM关闭时运行的钩子函数,它会进行销毁单例对象,关闭容器对象,重置监听集合等操作
            // 之后调用容器的refresh方法,执行容器的刷新逻辑
            // 如果容器类型是AnnotationConfigServletWebServerApplicationContext,调用onRefresh()时会通过createWebServer()方法去创建Web服务器,就是new了一个Tomcat
			refreshContext(context);
            // 方法为空,此处为Application的一个扩展点
			afterRefresh(context, applicationArguments);
            // 停止计时,打印日志
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            // 调用监听器started方法
			listeners.started(context);
            // 获得ApplicationRunner和CommandLineRunner集合并排序,遍历调用它们的run方法,此处为一个扩展点
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
            // 打印异常,异常记录
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}
        // 此时环境已启动完成,最后在此处调用监听器的running方法
		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

prepareEnvironment逻辑:

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
        // 根据Web应用类型构建环境对象,构建时会读取配置的变量
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 将传入的环境变量配置到应用环境中
		configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 将configurationProperties放到最后
		ConfigurationPropertySources.attach(environment);
        // 这里发布了一个ApplicationEnvironmentPreparedEvent事件,读取全局配置文件
		listeners.environmentPrepared(environment);
        // 将配置的spring.main属性绑定到当前SpringApplication中
		bindToSpringApplication(environment);
        // 如果不是自定义环境,则将environment转换为当前应用类型对应的ConfigurableEnvironment中
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
        // 更新PropertySources
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

 

prepareContext逻辑:

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        // 给容器设置刚才加载的环境信息
		context.setEnvironment(environment);
        // 给容器设置Bean名称生成对象,资源加载策略对象,类型转换对象
		postProcessApplicationContext(context);
        // 从容器对象获得刚才创建应用对象设置的ApplicationContextInitializer集合,遍历调用initialize方法
		applyInitializers(context);
        // 用刚才加载的SpringApplicationRunListener集合对容器发布监听
		listeners.contextPrepared(context);
        // 默认开启,记录启动日志和配置文件信息
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
        // 从容器中获得beanFactory对象
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 注册当前参数对象到单例池
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        // 注册logo打印对象
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
        // 设置allowBeanDefinitionOverriding值,它默认为true,注册同名Bean时会覆盖
        // 这里设置不让它去覆盖,注册同名Bean则抛异常
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
        // 是否懒加载,默认false
        // 如果是true,则这里向容器添加LazyInitializationBeanFactoryPostProcessor
        // 它实现了BeanFactoryPostProcessor,实现逻辑是获得BeanDefinition,将他们的LazyInit设置为true
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
        // 获得sources,这里是启动类
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
        // 创建BeanDefinitionLoader,通过它的AnnotatedBeanDefinitionReader将启动类注册到容器中
		load(context, sources.toArray(new Object[0]));
        // 调用监听的contextLoaded方法
		listeners.contextLoaded(context);
	}

 

总结:

我们的启动类上加了 @SpringBootApplication 注解,它由 @EnableAutoConfiguration注解组成,里面Import了一个AutoConfigurationImportSelector类:它实现了DeferredImportSelector接口,返回一个 AutoConfigurationGroup对象,后续Spring调用它的selectImports方法获得对象列表去加载,实现自动装配。

启动过程:首先创建一个SpringApplication对象,它SPI加载了一些类到SpringApplication中,然后调用run方法;

run方法中根据应用环境创建对应的环境上下文对象,然后调用prepareContext方法准备环境,里面设置了当前环境信息,注册当前启动类到容器中;

之后调用容器对象的refresh方法进行环境上下文构建,重写了onRefresh方法进行去创建Web服务器对象。

 

热部署原理:

我们知道,在项目中引入spring-boot-devtools依赖即可实现热部署功能,那么这个依赖做了什么呢?

spring.factories:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.devtools.restart.RestartApplicationListener,\
org.springframework.boot.devtools.logger.DevToolsLogFactory.Listener

刚才有写到,springboot程序启动时首先会new一个SpringApplication对象,那么里面的:

		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

这行代码SPI加载了ApplicationListener相关类,那么引入的devtools依赖中ApplicationListener也会被加载。

后续run方法执行中listeners.starting();会启动监听

最终会执行RestartApplicationListener的onApplicationEvent方法:

	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationStartingEvent) {
			onApplicationStartingEvent((ApplicationStartingEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent((ApplicationPreparedEvent) event);
		}
		if (event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) {
			Restarter.getInstance().finish();
		}
		if (event instanceof ApplicationFailedEvent) {
			onApplicationFailedEvent((ApplicationFailedEvent) event);
		}
	}

此处为starting监听,走starting方法

	private void onApplicationStartingEvent(ApplicationStartingEvent event) {
		// It's too early to use the Spring environment but we should still allow
		// users to disable restart using a System property.
        // 获得spring.devtools.restart.enabled配置信息
		String enabled = System.getProperty(ENABLED_PROPERTY);
        // 配置为空或配置为true
		if (enabled == null || Boolean.parseBoolean(enabled)) {
			String[] args = event.getArgs();
            // 默认的重启对象
			DefaultRestartInitializer initializer = new DefaultRestartInitializer();
            // 判断当前是否已经有了热部署功能,没有则restartOnInitialize为true
			boolean restartOnInitialize = !AgentReloader.isActive();
			if (!restartOnInitialize) {
				logger.info("Restart disabled due to an agent-based reloader being active");
			}
            // 初始化重启的主逻辑
			Restarter.initialize(args, false, initializer, restartOnInitialize);
		}
		else {
			logger.info(LogMessage.format("Restart disabled due to System property '%s' being set to false",
					ENABLED_PROPERTY));
			Restarter.disable();
		}
	}

    // 构建Restarter对象,调用它的initialize方法
	public static void initialize(String[] args, boolean forceReferenceCleanup, RestartInitializer initializer,
			boolean restartOnInitialize) {
		Restarter localInstance = null;
		synchronized (INSTANCE_MONITOR) {
			if (instance == null) {
				localInstance = new Restarter(Thread.currentThread(), args, forceReferenceCleanup, initializer);
				instance = localInstance;
			}
		}
		if (localInstance != null) {
			localInstance.initialize(restartOnInitialize);
		}
	}


    // 记录类编译后路径,调用immediateRestart方法
	protected void initialize(boolean restartOnInitialize) {
		preInitializeLeakyClasses();
		if (this.initialUrls != null) {
			this.urls.addAll(Arrays.asList(this.initialUrls));
			if (restartOnInitialize) {
				this.logger.debug("Immediately restarting application");
				immediateRestart();
			}
		}
	}

热部署的核心刷新逻辑都在immediateRestart这个方法里了:

	private void immediateRestart() {
		try {
            // callAndWait使用join方法保证结果执行完后返回
			getLeakSafeThread().callAndWait(() -> {
                // 重启的主逻辑,异步线程
				start(FailureHandler.NONE);
				cleanupCaches(); // 清除缓存
				return null;
			});
		}
		catch (Exception ex) {
			this.logger.warn("Unable to initialize restarter", ex);
		}
        // 主线程抛出SilentExitException异常,退出
		SilentExitExceptionHandler.exitCurrentThread();
	}

重启的主逻辑start:

	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]);
        // 构建自定义的类加载器RestartClassLoader
		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,它继承了Thread,重写了run方法
		RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName, this.args,
				this.exceptionHandler);
        // 开启线程,逻辑就是反射去执行启动类的main方法
		launcher.start();
		launcher.join();
        // 如果出现异常则此处返回
		return launcher.getError();
	}

    // RestartLauncher.run:
	public void run() {
		try {
			Class<?> mainClass = Class.forName(this.mainClassName, false, getContextClassLoader());
			Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
			mainMethod.invoke(null, new Object[] { this.args });
		}
		catch (Throwable ex) {
			this.error = ex;
			getUncaughtExceptionHandler().uncaughtException(this, ex);
		}
	}

 

那么Springboot是怎么知道该在什么时候进行重启的呢?

在spring.factories中存在org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration类,它会通过@Bean注入一个ClassPathFileSystemWatcher:

		@Bean
		@ConditionalOnMissingBean
		ClassPathFileSystemWatcher classPathFileSystemWatcher(FileSystemWatcherFactory fileSystemWatcherFactory,
				ClassPathRestartStrategy classPathRestartStrategy) {
            // 资源路径数组
			URL[] urls = Restarter.getInstance().getInitialUrls();
            // 根据文件系统观察工厂,重启触发策略,资源路径构建一个watcher对象
			ClassPathFileSystemWatcher watcher = new ClassPathFileSystemWatcher(fileSystemWatcherFactory,
					classPathRestartStrategy, urls);
            // 设置true,重启时停止监视
			watcher.setStopWatcherOnRestart(true);
			return watcher;
		}

接下来看一下文件系统观察工厂对象的逻辑:

		@Bean
		FileSystemWatcherFactory fileSystemWatcherFactory() {
			return this::newFileSystemWatcher;
		}

		private FileSystemWatcher newFileSystemWatcher() {
            // 获得重启的配置参数对象
			Restart restartProperties = this.properties.getRestart();
            // 根据轮询间隔(1s)和平静时间(0.4s)构建观察对象
			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.addSourceDirectory(path.getAbsoluteFile());
			}
			return watcher;
		}

重启触发策略对象的逻辑:

		@Bean
		@ConditionalOnMissingBean
		ClassPathRestartStrategy classPathRestartStrategy() {
            // 根据排除文件构建一个PatternClassPathRestartStrategy
			return new PatternClassPathRestartStrategy(this.properties.getRestart().getAllExclude());
		}

	public PatternClassPathRestartStrategy(String[] excludePatterns) {
		this.excludePatterns = excludePatterns;
	}

    // 判断如果改变的文件存在于排除路径中则返回false,即不需要完全重启
	public boolean isRestartRequired(ChangedFile file) {
		for (String pattern : this.excludePatterns) {
			if (this.matcher.match(pattern, file.getRelativeName())) {
				return false;
			}
		}
		return true;
	}

ClassPathFileSystemWatcher实现了InitializingBean重写了它的afterPropertiesSet方法,那么会在Bean填充属性后执行:

	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));
		}
        // 调用文件系统观察器start方法,开始监视文件的更改
		this.fileSystemWatcher.start();
	}

	public void start() {
		synchronized (this.monitor) {
            // 遍历文件,记录文件的时间和路径相关信息
			saveInitialSnapshots();
			if (this.watchThread == null) {
				Map<File, DirectorySnapshot> localDirectories = new HashMap<>(this.directories);
                // 使用Watcher对象创建线程并启动
				this.watchThread = new Thread(new Watcher(this.remainingScans, new ArrayList<>(this.listeners),
						this.triggerFilter, this.pollInterval, this.quietPeriod, localDirectories));
				this.watchThread.setName("File Watcher");
				this.watchThread.setDaemon(this.daemon);
				this.watchThread.start();
			}
		}
	}

那么start会执行Watcher.run方法:

		public void run() {
            // 剩余的扫描次数,创建时为-1,即一直执行
            // 如果调用了停止方法,会置为0,即停止
            // 大于0的话每次执行都会-1
			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 {
            // 每次执行睡眠1-0.4秒
			Thread.sleep(this.pollInterval - this.quietPeriod);
			Map<File, DirectorySnapshot> previous;
			Map<File, DirectorySnapshot> current = this.directories;
			do {
                // 上次文件信息
				previous = current;
                // 当前文件信息
				current = getCurrentSnapshots();
                // 获取后先睡0.4s
				Thread.sleep(this.quietPeriod);
			}
            // 当前信息和上次信息不一样则继续循环
            // 即文件0.4s期间一直在修改,继续循环
			while (isDifferent(previous, current));
            // 当前文件信息和上次信息不一样,则发布ClassPathChangedEvent,并更新当前文件信息
            // 即如果修改文件内容后,又撤销了改动,则不去重新加载
			if (isDifferent(this.directories, current)) {
				updateSnapshots(current.values());
			}
		}

 

发布监听后,定义好的监听就会执行对应逻辑

LocalDevToolsAutoConfiguration.RestartConfiguration.restartingClassPathChangedEventListener(FileSystemWatcherFactory):

		@Bean
		ApplicationListener<ClassPathChangedEvent> restartingClassPathChangedEventListener(
				FileSystemWatcherFactory fileSystemWatcherFactory) {
			return (event) -> {
				if (event.isRestartRequired()) {
					Restarter.getInstance().restart(new FileWatchingFailureHandler(fileSystemWatcherFactory));
				}
			};
		}

直接调用了restart方法:

	public void restart(FailureHandler failureHandler) {
		if (!this.enabled) {
			this.logger.debug("Application restart is disabled");
			return;
		}
		this.logger.debug("Restarting application");
		getLeakSafeThread().call(() -> {
			Restarter.this.stop();
			Restarter.this.start(failureHandler);
			return null;
		});
	}

可以看到使用了callable,先stop停止容器然后start启动

 

总结:

我们引入热部署依赖后,Spring通过SPI加载了热部署的监听,并在应用启动时发布监听;

注入的ClassPathFileSystemWatcher中afterPropertiesSet方法会开启一个线程一直循环去比较文件的修改时间大小相关信息(默认1s),当文件发生改变时发布ClassPathChangedEvent监听;

触发监听逻辑,会使用自定义的类加载器RestartClassLoader,通过反射去执行启动类的main方法,完成重启。

 

---------------------------------------------------------------------------------------------

Spring boot jar包执行原理:

首先可以看一下一个普通的Maven项目所打成的jar包,打开jar包后的META-INF目录下MANIFEST.MF文件内容:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: 
Build-Jdk: 1.8.0_131
Main-Class: com.zmy.Start

这里的Main-Class是我指定的执行类,在使用java -jar命令去执行jar包时,会自动去执行这个Main-Class的main方法。

然后对比一下一个spring boot项目打成的jar中META-INF目录下MANIFEST.MF文件内容:

Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: demo2
Implementation-Version: 0.0.1-SNAPSHOT
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.example.demo2.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.5.3
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

看一下这里的Main-Class为org.springframework.boot.loader.JarLauncher,那么使用java -jar命令去执行jar包时,执行的就是这个类中的main方法。接下来分析一下这个类干了什么:

(注:此部分源码在spring-boot-loader依赖中)

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

直接创建了一个JarLauncher并调用launch方法,这里它继承了Launcher类,最终执行的是Launcher中的方法:

	protected void launch(String[] args) throws Exception {
        // 是否爆炸模式,默认false
		if (!isExploded()) {
            // 设置java.protocol.handler.pkgs参数为org.springframework.boot.loader
			JarFile.registerUrlProtocolHandler();
		}
        // getClassPathArchivesIterator方法获得归档对象的集合,这里是用于描述jar包的信息JarFileArchive
        // createClassLoader则遍历归档对象集合,获得url数据去创建LaunchedURLClassLoader对象
        // LaunchedURLClassLoader继承了URLClassLoader做了一些扩展
		ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
		String jarMode = System.getProperty("jarmode");
		String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
        // 执行main方法的主逻辑
		launch(args, launchClass, classLoader);
	}

可以看到这里主要就是获得了jar路径去创建一个URLClassLoader,接下来看launch逻辑:

	protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
        // 将刚才创建的URLClassLoader设置为当前线程的类加载器
		Thread.currentThread().setContextClassLoader(classLoader);
        // 根据主启动类和参数创建一个MainMethodRunner,执行run方法
		createMainMethodRunner(launchClass, args, classLoader).run();
	}

	public void run() throws Exception {
		Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		mainMethod.setAccessible(true);
		mainMethod.invoke(null, new Object[] { this.args });
	}

可以看到这里是通过反射去执行了我们定义的Start-Class类的main方法,即我们spring boot应用的主启动类。

 

总结:

Spring Boot应用打包之后,生成一个Fat jar,包含了应用依赖的jar包和Spring Boot loader相关的类。

Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载/lib下面的jar,启动应用的Main函数。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部