配置
在Program arguments
配置2个参数:--server.port=8081 --spring.profiles.active=dev
。
run方法
run
方法执行结束代表SpringBoot
启动完成,即完成加载bean。
// ConfigurableApplicationContext 是IOC容器
public ConfigurableApplicationContext run(String... args) {// 1. 启动计时Startup startup = Startup.create();if (this.registerShutdownHook) {// shutdownHook是JVM正常或者异常退出时执行的方法,此处`enableShutdownHookAddition`不是已经添加方法,只是标记可以添加shutdownHook方法。SpringApplication.shutdownHook.enableShutdownHookAddition();}// 2. 创建引导上下文DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null; // context 就是ioc容器configureHeadlessProperty(); // 允许JVM在没有显示器、鼠标、键盘等外设的情况工作,web项目也用不着外设// 3. 加载监听器SpringApplicationRunListeners listeners = getRunListeners(args);// 4. 执行监听器starting方法listeners.starting(bootstrapContext, this.mainApplicationClass);try {// args是spirngboot main方法的参数,将其封装为ApplicationArguments类ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 5. 准备环境ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 打印bannerBanner printedBanner = printBanner(environment);// 创建IOC容器context = createApplicationContext();// 开始配置IOC容器context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);// 结束配置IOC容器startup.started();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);}listeners.started(context, startup.timeTakenToStarted());// 执行`Runner`方法,可以用于项目启动后删除敏感配置文件callRunners(context, applicationArguments);}catch (Throwable ex) {throw handleRunFailure(context, ex, listeners);}try {if (context.isRunning()) {listeners.ready(context, startup.ready());}}catch (Throwable ex) {throw handleRunFailure(context, ex, null);}return context;
}
1. 启动计时
org.springframework.boot.SpringApplication.Startup
是计时类,用于统计启动时间。CoordinatedRestoreAtCheckpointStartup
是针对CRac(Coordinated Restore at Checkpoint)
项目的计时实现类。CRac
项目可以对JVM
状态建立快照,并且存入磁盘。之后将JVM
状态从保存的检查点恢复到内存。
普通SpringBoot
项目用的是StandardStartup
。
abstract static class Startup {private Duration timeTakenToStarted;protected abstract long startTime();protected abstract Long processUptime();protected abstract String action();final Duration started() {long now = System.currentTimeMillis();this.timeTakenToStarted = Duration.ofMillis(now - startTime());return this.timeTakenToStarted;}Duration timeTakenToStarted() {return this.timeTakenToStarted;}private Duration ready() {long now = System.currentTimeMillis();return Duration.ofMillis(now - startTime());}static Startup create() {ClassLoader classLoader = Startup.class.getClassLoader();return (ClassUtils.isPresent("jdk.crac.management.CRaCMXBean", classLoader)&& ClassUtils.isPresent("org.crac.management.CRaCMXBean", classLoader))? new CoordinatedRestoreAtCheckpointStartup() : new StandardStartup();}
}
private static final class StandardStartup extends Startup {private final Long startTime = System.currentTimeMillis(); // 加载StandardStartup类对象的时间@Overrideprotected long startTime() {return this.startTime;}@Overrideprotected Long processUptime() {try {return ManagementFactory.getRuntimeMXBean().getUptime();}catch (Throwable ex) {return null;}}@Overrideprotected String action() {return "Started";}
}
2. 创建引导上下文
创建一个上下文。用从META-INF/spring.factories
文件加载的初始化器初始化引导上下文。
普通SpringBoot
项目用不着这个。
private DefaultBootstrapContext createBootstrapContext() {DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));return bootstrapContext;
}
3. 加载监听器
这个方法从2个地方获取监听器类。一个是META-INF/spring.factories
,一个是hook
。
SpringApplicationRunListeners
对象是对监听器类的封装。
private SpringApplicationRunListeners getRunListeners(String[] args) {ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);argumentResolver = argumentResolver.and(String[].class, args);// 在`META-INF/spring.factories`中加载`SpringApplicationRunListener.class`的实现类。List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,argumentResolver);SpringApplicationHook hook = applicationHook.get();SpringApplicationRunListener hookListener = (hook != null) ? hook.getRunListener(this) : null;if (hookListener != null) {listeners = new ArrayList<>(listeners);// 加载hook的监听类listeners.add(hookListener);}return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);
}
自己实现一个监听器类,并且在META-INF/spring.factories
文件中加一行org.springframework.boot.SpringApplicationRunListener=com.example.demo.MyAppListener
。
public class MyAppListener implements SpringApplicationRunListener {@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {System.out.println("正在启动");SpringApplicationRunListener.super.starting(bootstrapContext);}@Overridepublic void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {System.out.println("环境已经准备好了");SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("上下文准备好了");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("ioc加载完成了");}@Overridepublic void started(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("启动完成");}@Overridepublic void ready(ConfigurableApplicationContext context, Duration timeTaken) {System.out.println("准备就绪");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("应用启动失败");}
}
SpringBoot
加载2个监听器类。其中EventPublishingRunListener
是Spring
自带的。
4. 执行监听器starting方法
SpringApplicationRunListeners
的成员变量listeners
存储所有监听器。
class SpringApplicationRunListeners {private final Log log;private final List<SpringApplicationRunListener> listeners;private final ApplicationStartup applicationStartup;SpringApplicationRunListeners(Log log, List<SpringApplicationRunListener> listeners,ApplicationStartup applicationStartup) {this.log = log;this.listeners = List.copyOf(listeners);this.applicationStartup = applicationStartup;}void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),(step) -> {if (mainApplicationClass != null) {step.tag("mainApplicationClass", mainApplicationClass.getName());}});}
}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,Consumer<StartupStep> stepAction) {// StartupStep 不是`Startup`,后者是计时器,前者表示执行阶段StartupStep step = this.applicationStartup.start(stepName);// 这个语句就是`this.listeners.forEach((listener) -> listener.starting(bootstrapContext))`,执行监听器的starting方法this.listeners.forEach(listenerAction);if (stepAction != null) {stepAction.accept(step);}step.end();}
控制台输出正在启动
。
5. 准备环境
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// Create and configure the environment// environment的实际类型是ApplicationServletEnvironmentConfigurableEnvironment environment = getOrCreateEnvironment();// 1. 配置环境configureEnvironment(environment, applicationArguments.getSourceArgs());// 2. 新增`ConfigurationPropertySource`ConfigurationPropertySources.attach(environment);listeners.environmentPrepared(bootstrapContext, environment);DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");bindToSpringApplication(environment);if (!this.isCustomEnvironment) {EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;
}
1. 配置环境
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {if (this.addConversionService) {// 配置默认的类型转化器`converters`和格式化器`formatters`environment.setConversionService(new ApplicationConversionService());}configurePropertySources(environment, args);configureProfiles(environment, args);
}
configurePropertySources
方法将Program arguments
封装为SimpleCommandLinePropertySource
传入environment
对象的propertysources
SpringApplication
未实现configureProfiles
方法.
2. 新增ConfigurationPropertySource
public static void attach(Environment environment) {Assert.isInstanceOf(ConfigurableEnvironment.class, environment);MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();PropertySource<?> attached = getAttached(sources);if (!isUsingSources(attached, sources)) {attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,new SpringConfigurationPropertySources(sources));}sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);sources.addFirst(attached);
}
这个方法的目的是将environment.propertySources
添加一个configurationproperties
,configurationproperties
是对environment.propertySources
的封装,目的是借助propertyresolver
对象解析属性。