启动流程
应用初始化
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
推断web应用类型
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
return REACTIVE;
} else {
String[] var0 = SERVLET_INDICATOR_CLASSES;
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
String className = var0[var2];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return NONE;
}
}
return SERVLET;
}
}
加载ApplicationContextInitializer
从spring.factories文件加载ApplicationContextInitializer
加载ApplicationListener
从spring.factories文件加载ApplicationListener
推断主类
根据调用栈推断主类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
}
return null;
}
应用启动
加载运行监听器
从spring.factories文件加载SpringApplicationRunListener,只获取到EventPublishingRunListener(用于事件监听),它持有1个广播器(间接持有ApplicationListener),广播器会根据事件类型筛选ApplicationListener
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
发布启动事件
有4个listener可以处理
构建日志系统(LoggingApplicationListener)
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
public static LoggingSystem get(ClassLoader classLoader) {
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
return (LoggingSystem)("none".equals(loggingSystem) ? new LoggingSystem.NoOpLoggingSystem() : get(classLoader, loggingSystem));
} else {
return (LoggingSystem)SYSTEMS.entrySet().stream().filter((entry) -> {
return ClassUtils.isPresent((String)entry.getKey(), classLoader);
}).map((entry) -> {
return get(classLoader, (String)entry.getValue());
}).findFirst().orElseThrow(() -> {
return new IllegalStateException("No suitable logging system located");
});
}
}
static {
Map<String, String> systems = new LinkedHashMap();
systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory", "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}
利用classloader依次加载logback、log4j2、jul日志系统,谁先加载成功就使用它,这也是为什么springboot底层默认使用的是logback。
后台预热关键组件(BackgroudPreinitializer)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {
public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";
private static final AtomicBoolean preinitializationStarted = new AtomicBoolean(false);
private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);
public BackgroundPreinitializer() {
}
public void onApplicationEvent(SpringApplicationEvent event) {
if (!Boolean.getBoolean("spring.backgroundpreinitializer.ignore") && event instanceof ApplicationStartingEvent && this.multipleProcessors() && preinitializationStarted.compareAndSet(false, true)) {
this.performPreinitialization();
}
if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) && preinitializationStarted.get()) {
try {
preinitializationComplete.await();
} catch (InterruptedException var3) {
Thread.currentThread().interrupt();
}
}
}
private void performPreinitialization() {
try {
Thread thread = new Thread(new Runnable() {
public void run() {
this.runSafely(new BackgroundPreinitializer.ConversionServiceInitializer());
this.runSafely(new BackgroundPreinitializer.ValidationInitializer());
this.runSafely(new BackgroundPreinitializer.MessageConverterInitializer());
this.runSafely(new BackgroundPreinitializer.JacksonInitializer());
this.runSafely(new BackgroundPreinitializer.CharsetInitializer());
BackgroundPreinitializer.preinitializationComplete.countDown();
}
public void runSafely(Runnable runnable) {
try {
runnable.run();
} catch (Throwable var3) {
}
}
}, "background-preinit");
thread.start();
} catch (Exception var2) {
preinitializationComplete.countDown();
}
}
创建后台线程预热ConversionServiceInitializer、ValidationInitializer、MessageConverterInitializer、JacksonInitializer、CharsetInitializer组件,因为这些组件挺耗时。
准备environment
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
//把ConfigurationPropertySourcesPropertySource作为第1个PropertySource
ConfigurationPropertySources.attach((Environment)environment);
listeners.environmentPrepared((ConfigurableEnvironment)environment);
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
//删除ConfigurationPropertySourcesPropertySource
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
} else {
switch(this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
}
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService)conversionService);
}
this.configurePropertySources(environment, args);
this.configureProfiles(environment, args);
}
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties && args.length > 0) {
String name = "commandLineArgs";
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
} else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
Set<String> profiles = new LinkedHashSet(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
environment最终持有的PropertySource
总结
1.把命令行参数封装成ApplicationArguments
2.创建StandardServletEnvironment,这个过程中先后添加了servlet环境变量、java系统属性(比如-Dspring.profiles.active)、操作系统环境变量
3.给environment设置ApplicationConversionService
4.把命令行参数解析成k,v后封装成SimpleCommandLinePropertySource,添加到environment最前面,方便后续优先获取
5.设置environment的activeProfiles,解析参数--spring.profiles.active
6.把ConfigurationPropertySourcesPropertySource添加到environment最前面
7.发布environment准备好事件
发布environment准备好事件
支持环境就绪事件的listener
解析配置文件(ConfigFileApplicationListener)
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
//从spring.factories文件加载EnvironmentPostProcessor
List<EnvironmentPostProcessor> postProcessors = this.loadPostProcessors();
//把自己也添加进去
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
Iterator var3 = postProcessors.iterator();
while(var3.hasNext()) {
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var3.next();
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
排序后的EnvironmentPostProcessor
总结
1.从spring.factories文件加载EnvironmentPostProcessor到postProcessors中
2.把自身也添加进postProcessors,因为它也实现了EnvironmentPostProcessor
3.对processors进行排序,遍历调用postProcessEnvironment方法
初始化日志系统(LoggingApplicationListener)
public class LoggingApplicationListener implements GenericApplicationListener {
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
this.initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
//把一些默认的日志属性设置成java系统属性
(new LoggingSystemProperties(environment)).apply();
this.logFile = LogFile.get(environment);
if (this.logFile != null) {
this.logFile.applyToSystemProperties();
}
this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
//根据debug、trace属性来设置日志级别LogLevel.DEBUG、LogLevel.TRACE
this.initializeEarlyLoggingLevel(environment);
//根据logging.config属性获取到日志的配置文件路径,初始化日志系统
this.initializeSystem(environment, this.loggingSystem, this.logFile);
//初始化最终的日志级别
this.initializeFinalLoggingLevels(environment, this.loggingSystem);
this.registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) {
if (this.parseArgs && this.springBootLogging == null) {
if (this.isSet(environment, "debug")) {
this.springBootLogging = LogLevel.DEBUG;
}
if (this.isSet(environment, "trace")) {
this.springBootLogging = LogLevel.TRACE;
}
}
}
private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
String logConfig = environment.getProperty("logging.config");
if (this.ignoreLogConfig(logConfig)) {
system.initialize(initializationContext, (String)null, logFile);
} else {
try {
ResourceUtils.getURL(logConfig).openStream().close();
system.initialize(initializationContext, logConfig, logFile);
} catch (Exception var7) {
System.err.println("Logging system failed to initialize using configuration from '" + logConfig + "'");
var7.printStackTrace(System.err);
throw new IllegalStateException(var7);
}
}
}
private void initializeFinalLoggingLevels(ConfigurableEnvironment environment, LoggingSystem system) {
this.bindLoggerGroups(environment);
if (this.springBootLogging != null) {
this.initializeLogLevel(system, this.springBootLogging);
}
this.setLogLevels(system, environment);
}
}
public class LoggingSystemProperties {
public static final String PID_KEY = "PID";
public static final String EXCEPTION_CONVERSION_WORD = "LOG_EXCEPTION_CONVERSION_WORD";
public static final String LOG_FILE = "LOG_FILE";
public static final String LOG_PATH = "LOG_PATH";
public static final String CONSOLE_LOG_PATTERN = "CONSOLE_LOG_PATTERN";
public static final String FILE_CLEAN_HISTORY_ON_START = "LOG_FILE_CLEAN_HISTORY_ON_START";
public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN";
public static final String FILE_MAX_HISTORY = "LOG_FILE_MAX_HISTORY";
public static final String FILE_MAX_SIZE = "LOG_FILE_MAX_SIZE";
public static final String FILE_TOTAL_SIZE_CAP = "LOG_FILE_TOTAL_SIZE_CAP";
public static final String LOG_LEVEL_PATTERN = "LOG_LEVEL_PATTERN";
public static final String LOG_DATEFORMAT_PATTERN = "LOG_DATEFORMAT_PATTERN";
public static final String ROLLING_FILE_NAME_PATTERN = "ROLLING_FILE_NAME_PATTERN";
private final Environment environment;
public LoggingSystemProperties(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
public void apply() {
this.apply((LogFile)null);
}
public void apply(LogFile logFile) {
//持有environment的PropertySources
PropertyResolver resolver = this.getPropertyResolver();
this.setSystemProperty(resolver, "LOG_EXCEPTION_CONVERSION_WORD", "exception-conversion-word");
this.setSystemProperty("PID", (new ApplicationPid()).toString());
this.setSystemProperty(resolver, "CONSOLE_LOG_PATTERN", "pattern.console");
this.setSystemProperty(resolver, "FILE_LOG_PATTERN", "pattern.file");
this.setSystemProperty(resolver, "LOG_FILE_CLEAN_HISTORY_ON_START", "file.clean-history-on-start");
this.setSystemProperty(resolver, "LOG_FILE_MAX_HISTORY", "file.max-history");
this.setSystemProperty(resolver, "LOG_FILE_MAX_SIZE", "file.max-size");
this.setSystemProperty(resolver, "LOG_FILE_TOTAL_SIZE_CAP", "file.total-size-cap");
this.setSystemProperty(resolver, "LOG_LEVEL_PATTERN", "pattern.level");
this.setSystemProperty(resolver, "LOG_DATEFORMAT_PATTERN", "pattern.dateformat");
this.setSystemProperty(resolver, "ROLLING_FILE_NAME_PATTERN", "pattern.rolling-file-name");
if (logFile != null) {
logFile.applyToSystemProperties();
}
}
}
总结:
1.把日志系统的一些默认属性设置成java系统属性
2.根据debug、trace属性来设置对应的日志级别
3.根据logging.config属性来获取到配置文件,并初始化日志系统
4.确定最终日志级别
事件转播(DelegatingApplicationListener)
public class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent>, Ordered {
private static final String PROPERTY_NAME = "context.listener.classes"
private int order = 0;
private SimpleApplicationEventMulticaster multicaster;
public DelegatingApplicationListener() {
}
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
List<ApplicationListener<ApplicationEvent>> delegates = this.getListeners(((ApplicationEnvironmentPreparedEvent)event).getEnvironment());
if (delegates.isEmpty()) {
return;
}
this.multicaster = new SimpleApplicationEventMulticaster();
Iterator var3 = delegates.iterator();
while(var3.hasNext()) {
ApplicationListener<ApplicationEvent> listener = (ApplicationListener)var3.next();
this.multicaster.addApplicationListener(listener);
}
}
if (this.multicaster != null) {
this.multicaster.multicastEvent(event);
}
}
private List<ApplicationListener<ApplicationEvent>> getListeners(ConfigurableEnvironment environment) {
if (environment == null) {
return Collections.emptyList();
} else {
String classNames = environment.getProperty("context.listener.classes");
List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList();
if (StringUtils.hasLength(classNames)) {
Iterator var4 = StringUtils.commaDelimitedListToSet(classNames).iterator();
while(var4.hasNext()) {
String className = (String)var4.next();
try {
Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationListener.class, clazz, "class [" + className + "] must implement ApplicationListener");
listeners.add((ApplicationListener)BeanUtils.instantiateClass(clazz));
} catch (Exception var7) {
throw new ApplicationContextException("Failed to load context listener class [" + className + "]", var7);
}
}
}
AnnotationAwareOrderComparator.sort(listeners);
return listeners;
}
}
}
总结
1.在收到应用启动事件时会创建context.listener.classes指定的ApplicationListener,把这些listener加入广播器中。
2.后续收到的所有事件再转播给这些listener,起到了1个代理作用
3.扩展点,第2种添加ApplicationListener的方法,第1种是通过spring.factories文件配置
打印Banner
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = this.getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new SpringApplicationBannerPrinter.PrintedBanner(banner, sourceClass);
}
private Banner getBanner(Environment environment) {
SpringApplicationBannerPrinter.Banners banners = new SpringApplicationBannerPrinter.Banners();
banners.addIfNotNull(this.getImageBanner(environment));
banners.addIfNotNull(this.getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
} else {
return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
}
}
private Banner getTextBanner(Environment environment) {
//默认取类路径下的banner.txt
String location = environment.getProperty("spring.banner.location", "banner.txt");
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ResourceBanner(resource) : null;
}
private Banner getImageBanner(Environment environment) {
String location = environment.getProperty("spring.banner.image.location");
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ImageBanner(resource) : null;
} else {
String[] var3 = IMAGE_EXTENSION;
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
String ext = var3[var5];
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}
}
总结
1.文本banner扩展
类路径下放1个banner.txt文件
通过spring.banner.location属性指定路径
2.图案banner扩展
类路径下放banner.gif、banner.jpg、banner.png文件
通过spring.banner.image.location属性指定路径
准备context
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
this.applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
context.getBeanFactory().registerSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator", this.beanNameGenerator);
}
if (this.resourceLoader != null) {
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext)context).setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader)context).setClassLoader(this.resourceLoader.getClassLoader());
}
}
if (this.addConversionService) {
context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
}
}
protected void applyInitializers(ConfigurableApplicationContext context) {
Iterator var2 = this.getInitializers().iterator();
while(var2.hasNext()) {
ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
总结
1.创建AnnotationConfigServletWebServerApplicationContext
2.使用从spring.factories文件获取的初始化器对context进行初始化
3.发布context初始化完成事件
4.把命令行参数注册到context
5.设置context的allowBeanDefinitionOverriding属性
6.把主类注册到context
7.发布应用准备好事件
context初始化
总结
这儿可以对context做一些扩展,比如添加后置处理器、注册bean等
发布context初始化完成事件
总结
1.BackgroundPreinitializer啥也没干
2.DelegatingApplicationListener同前面一样,只是起代理作用
注册主类
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = this.createBeanDefinitionLoader(this.getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
发布应用准备好事件
public void contextLoaded(ConfigurableApplicationContext context) {
ApplicationListener listener;
for(Iterator var2 = this.application.getListeners().iterator(); var2.hasNext(); context.addApplicationListener(listener)) {
listener = (ApplicationListener)var2.next();
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware)listener).setApplicationContext(context);
}
}
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
有5个listener可以处理
总结
1.实现了ApplicationContextAware的listener会保存context
2.ConfigFileApplicationListener,添加了1个BeanFactoryPostProcessor=PropertySourceOrderingPostProcessor
4.LoggingApplicationListener,把日志系统作为单例注册到了ioc
刷新context
略,详见spring源码分析
发布context刷新完成事件
子类扩展实现。
发布应用启动完成事件
public void started(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}
总结
和前面的发布事件不同,没有通过多播器发布事件,而是直接通过context发布的。