> 文档中心 > Spring Boot启动那些事儿

Spring Boot启动那些事儿

文章目录

    • 1. SpringApplication实例的创建
    • 2. SpringApplicationRunListeners的开始
    • 3. SimpleApplicationEventMulticaster的事件广播
    • 4. ConfigurableEnvironment实例的创建与事件发布
    • 5. Banner的打印
    • 6. ConfigurableApplicationContext实例的创建与事件发布
    • 7. 启动成功日志的打印
    • 8. ApplicationStartedEvent与ApplicationReadyEvent事件的发布

1. SpringApplication实例的创建

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

上面这段代码应该是我们在Spring Boot应用中最熟悉不过的了,即便是一个新手也知道只要一运行main方法,然后整个应用就唰唰唰的运行起来了。

可就这SpringApplication.run(EurekaApplication.class, args);简单的一行代码,殊不知的Spring Boot在后面默默的为我们做了多少事情,咱们来看看!!!

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

这是我们调用的run方法的实现,其实就是调用了另一个重载的run方法,咱们直接看另一个;

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

在这个run方法里面,一共做了两件事情,一是创建了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");    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));    this.webApplicationType = WebApplicationType.deduceFromClasspath();    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));    this.mainApplicationClass = deduceMainApplicationClass();}

根据上面的代码进行分析,实例化SpringApplication对象的整个过程无非是对其一些属性进行赋值,那么我们先整理一下SpringApplication类有哪些属性,然后再看看实例化过程对哪些属性进行了定义;

/ * 非Web环境应用的默认上下文类名 */public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext";/ * 传统Web环境应用的默认上下文类名(阻塞式Web服务) */public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";/ * 响应Web环境应用的默认上下文类名(响应式Web服务) */public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework." + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";/ * 默认的Banner位置:banner.txt * 咱们只需要在项目的classpath目录下新建一个banner.txt文件, 并填充好自己的banner, 启动项目即可看见自定义的banner */public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;/ * Banner位置属性Key */public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;/ * Headless模式key */private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";/ * 日志记录实例 * 这里实际使用的是spring-jcl包里面的日志适配器 * 日志适配器适配顺序:LOG4J ==> SLF4J_LAL ==> SLF4J ==> JUL */private static final Log logger = LogFactory.getLog(SpringApplication.class);/ * 主要Bean资源, 其实就是run方法传递的那个Class参数, 平常我们传递的是启动类的Class实例 */private Set<Class<?>> primarySources;/ * 用于创建应用上下文的额外资源, 可以是一个类名、包名、或者XML文件路径 */private Set<String> sources = new LinkedHashSet<>();/ * main方法所在的Class对象 */private Class<?> mainApplicationClass;/ * Banner打印模式, 可选择OFF不打印、CONSOLE控制台打印、LOG日志打印 */private Banner.Mode bannerMode = Banner.Mode.CONSOLE;/ * 应用启动时日志信息是否记录 */private boolean logStartupInfo = true;/ * 启动命令行的参数是否添加到应用上下文中, 以CommandLinePropertySource实例方式 */private boolean addCommandLineProperties = true;/ * 转换器服务实例是否添加到应用上下文中 */private boolean addConversionService = true;/ * 当未提供静态的Banner文件时, 将使用该Banner实例 */private Banner banner;/ * 资源加载器 */private ResourceLoader resourceLoader;/ * Bean名称生成器 */private BeanNameGenerator beanNameGenerator;/ * 用于创建应用上下文的环境实例 */private ConfigurableEnvironment environment;/ * 应用上下文Class */private Class<? extends ConfigurableApplicationContext> applicationContextClass;/ * 应用类型: 非Web、传统Web、响应式Web */private WebApplicationType webApplicationType;/ * Headless模式是否开启 */private boolean headless = true;/ * 应用关闭的回调钩子是否允许注册 */private boolean registerShutdownHook = true;/ * 应用上下文初始化器集 */private List<ApplicationContextInitializer<?>> initializers;/ * 应用监听器集 */private List<ApplicationListener<?>> listeners;/ * 默认环境属性 */private Map<String, Object> defaultProperties;/ * 额外的配置文件 */private Set<String> additionalProfiles = new HashSet<>();/ * 当BeanDefinition有相同的BeanName时是否允许被重写 */private boolean allowBeanDefinitionOverriding;/ * 是否自定义环境 */private boolean isCustomEnvironment = false;/ * 是否懒加载初始化 */private boolean lazyInitialization = false;

现在整个SpringApplication类的静态与非静态属性已经分析完成,咱们再从新来看看创建SpringApplication实例对哪些属性进行了定义:

  1. this.resourceLoader = resourceLoader;

    初始化资源加载器,使用的是方法入参上的实例,但通过重载构造方法过来的是一个null,所以平常这个属性在此刻还是是一个null值;

  2. this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));

    初始化主要Bean资源,并通过Set集合进行了去重,因为平常我们都只传递了启动类的Class进入该方法,所以该属性的值一般都是仅有应用启动类Class一个元素的Set集合;

  3. this.webApplicationType = WebApplicationType.deduceFromClasspath();

    初始化Web应用的类型,咱们来看看!!!

    private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";static WebApplicationType deduceFromClasspath() {    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)     && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE;    }    for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) {     return WebApplicationType.NONE; }    }    return WebApplicationType.SERVLET;}

    根据上面这段代码来分析,要想是响应式Web应用的条件还是挺苛刻的,需要应用的classpath下存在org.springframework.web.reactive.DispatcherHandler类,而且同时不存在Servlet相关的org.springframework.web.servlet.DispatcherServletorg.glassfish.jersey.servlet.ServletContainer类才可以达到,否则只要同时找到javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext类那就是传统的Web应用,任何一个没找到就不能当作Web应用服务;

  4. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

    重点!!!初始化IOC容器(应用上下文)初始化器集,着重点在getSpringFactoriesInstances(ApplicationContextInitializer.class)这个方法,咱们看看

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {    return getSpringFactoriesInstances(type, new Class<?>[] {});}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {    ClassLoader classLoader = getClassLoader();    // Use names and ensure unique to protect against duplicates    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);    AnnotationAwareOrderComparator.sort(instances);    return instances;}@SuppressWarnings("unchecked")private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {    List<T> instances = new ArrayList<>(names.size());    for (String name : names) { try {     Class<?> instanceClass = ClassUtils.forName(name, classLoader);     Assert.isAssignable(type, instanceClass);     Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);     T instance = (T) BeanUtils.instantiateClass(constructor, args);     instances.add(instance); } catch (Throwable ex) {     throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); }    }    return instances;}

    根据上面代码,可以知道它是通过SpringFactoriesLoader这个加载器去加载对应Class的实现类名称,然后通过遍历加载的结果创建实例,最后排好顺序返回;

    关于SpringFactoriesLoader是怎么去执行的,在这里不做过多介绍,我们只需要知道它是读取的META-INF目录下的spring.factories文件即可

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

    重点!!!初始化应用监听器集,与上一点应用上下文初始化器集的初始化相同,不过值得注意的是应用监听器其实就是继承了Java事件监听接口的一个函数式接口,在后续的工作中有大量的工作都是依靠它完成的;

  6. this.mainApplicationClass = deduceMainApplicationClass();

    初始化main方法所在的Class对象,咱们看看它是怎么推断的

    private Class<?> deduceMainApplicationClass() {    try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) {     if ("main".equals(stackTraceElement.getMethodName())) {  return Class.forName(stackTraceElement.getClassName());     } }    }    catch (ClassNotFoundException ex) { // Swallow and continue    }    return null;}

    根据上面代码分析,其实就是创建了一个RuntimeException异常实例,然后遍历该异常实例的堆栈,去匹配main方法所在的Class的;

至此,SpringApplication实例已经创建完成,在创建过程中的重点在于对initializerslistenerswebApplicationType等属性进行了初始化定义。

2. SpringApplicationRunListeners的开始

接下来咱们开始分析SpringApplication实例的run方法,先看一下代码

public ConfigurableApplicationContext run(String... args) {    /* start */    StopWatch stopWatch = new StopWatch();    stopWatch.start();    ConfigurableApplicationContext context = null;    configureHeadlessProperty();    SpringApplicationRunListeners listeners = getRunListeners(args);    listeners.starting();    /* end */    try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) {     new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments);    }    catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex);    }    try { listeners.running(context);    }    catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex);    }    return context;}

整个run方法做的事情很多,咱们需要逐个进行分析,这里先分析方法体的1~6行代码;

StopWatch stopWatch = new StopWatch();
stopWatch.start();

这是前两行代码,不关乎主要流程,只作为应用启动过程的时间监控;

ConfigurableApplicationContext context = null;

这行代码定义了一个ConfigurableApplicationContext实例变量,可以跳过;

configureHeadlessProperty();

private void configureHeadlessProperty() {    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,     System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));}

这行代码配置了Headless模式是否开启,默认值为SpringApplication实例的headless属性的值(在未特殊指定的时候都是开启);

SpringApplicationRunListeners listeners = getRunListeners(args);

listeners.starting();

重点的两行代码!!!

先看第一行的getRunListeners方法

private SpringApplicationRunListeners getRunListeners(String[] args) {    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };    return new SpringApplicationRunListeners(logger,     getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {    ClassLoader classLoader = getClassLoader();    // Use names and ensure unique to protect against duplicates    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);    AnnotationAwareOrderComparator.sort(instances);    return instances;}

根据上面代码,其实比较好理解,因为getSpringFactoriesInstances这个方法在之前我们遇到过,就是从spring.factories文件中找到某个接口的实现类,那么在这里找的是SpringApplicationRunListener这个接口;注意:与之前的区别在于这里定义了该接口实现类构造方法的形参列表:SpringApplication.class、String[].class,所以咱们在自定义SpringApplicationRunListener实现类时,一定要有SpringApplication.class、String[].class形参的构造方法,否则Spring Boot帮你创建实例的时候会抛出异常;

然后将SpringApplicationRunListener接口实现类的实例集合同logger一起包装为SpringApplicationRunListeners实例,并返回run方法中;

接下来咱们看看SpringApplicationRunListeners的starting方法干了什么事情

void starting() {    for (SpringApplicationRunListener listener : this.listeners) { listener.starting();    }}

其实就是遍历了SpringApplicationRunListener实例集合,然后执行每个实例的starting方法;

接下来咱们看点扩展东西,找到spring-boot包下面的spring.factories文件,可以看到这项配置

# Run Listenersorg.springframework.boot.SpringApplicationRunListener=\org.springframework.boot.context.event.EventPublishingRunListener

也就是说上面的SpringApplicationRunListener实例集合中会包含 一个EventPublishingRunListener实例,它的starting方法也会被执行,咱们接着看看这个EventPublishingRunListener实例有些什么代码

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {private final SpringApplication application;private final String[] args;private final SimpleApplicationEventMulticaster initialMulticaster;public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;this.initialMulticaster = new SimpleApplicationEventMulticaster();for (ApplicationListener<?> listener : application.getListeners()) {this.initialMulticaster.addApplicationListener(listener);}}    // ......    @Overridepublic void starting() {this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));} // ......}

从代码来看,首先它的构造方法证明实现了SpringApplicationRunListener接口的类需要有一个SpringApplication.class和String[].class形参列表的构造方法;然后它在构造方法里初始化了initialMulticaster变量,这是一个简单应用事件广播器,再把SpringApplication实例中的应用监听器集添加到了这个广播器中;最后再看看starting方法,其实就是广播了一个应用正在启动的事件ApplicationStartingEvent;

3. SimpleApplicationEventMulticaster的事件广播

接下来,咱们继续看看这个应用事件广播器是怎么去运作的;

首先,像咱们之前分析SpringApplication一样先看看它有些什么属性

// SimpleApplicationEventMulticaster类的属性/ * 任务执行器, 它可以提供同步或异步的方式去执行一个事件的广播 */@Nullableprivate Executor taskExecutor;/ * 异常处理器, 用于执行事件广播时发生异常的时候 */@Nullableprivate ErrorHandler errorHandler;-----------------------------------------------------------------------------------------// AbstractApplicationEventMulticaster类的属性, 是SimpleApplicationEventMulticaster的父类/ * 存储着全部应用监听器实例和应用监听器BeanName的检索器, 是AbstractApplicationEventMulticaster的内部类 */private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();/ * 事件类型与事件监听器映射关系的检索缓存 */final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);/ * ClassLoader实例, 因为实现了BeanClassLoaderAware接口 */@Nullableprivate ClassLoader beanClassLoader;/ * BeanFactory实例, 因为实现了BeanFactoryAware接口 */@Nullableprivate ConfigurableBeanFactory beanFactory;

根据分析,其实SimpleApplicationEventMulticaster的属性并不多,结构也不复杂,那咱们就看看multicastEvent方法干了一些什么事情

@Overridepublic void multicastEvent(ApplicationEvent event) {    multicastEvent(event, resolveDefaultEventType(event));}

我们继续根据第2点结尾时的内容进行分析;

这里广播的是一个ApplicationStartingEvent事件,然后resolveDefaultEventType方法分解了事件类型,然后将事件和事件类型两个参数传递到了下面这个方法;

@Overridepublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));    Executor executor = getTaskExecutor();    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { if (executor != null) {     executor.execute(() -> invokeListener(listener, event)); } else {     invokeListener(listener, event); }    }}

ResolvableType知识扩展:这是Spring封装的Java类型,提供了对超类型、接口、泛型参数的访问,以及最终解析为Class的能力;ResolvableType的构造方法全部为私有化,可以通过其提供的静态方法从字段、方法参数、方法返回类型、Classes创建实例。

继续分析代码,方法体的第一行代码是再次判断入参eventType是否为null,如果为null则通过event事件参数进行分解然后赋值;

然后接下来代码的意思是通过父类的getApplicationListeners方法得到需要执行的应用监听器,遍历这些监听器进行一个个执行,只不过如果任务执行器不为空的话,就通过这个任务执行器去执行,否则就以正常方式同步执行;

那么它是怎么去匹配事件与监听器关系的呢?咱们看看getApplicationListeners方法

protected Collection<ApplicationListener<?>> getApplicationListeners(    ApplicationEvent event, ResolvableType eventType) {    Object source = event.getSource();    Class<?> sourceType = (source != null ? source.getClass() : null);    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);    // Potential new retriever to populate    CachedListenerRetriever newRetriever = null;    // Quick check for existing entry on ConcurrentHashMap    CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);    if (existingRetriever == null) { // Caching a new ListenerRetriever if possible if (this.beanClassLoader == null ||     (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&      (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {     newRetriever = new CachedListenerRetriever();     existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);     if (existingRetriever != null) {  newRetriever = null;  // no need to populate it in retrieveApplicationListeners     } }    }    if (existingRetriever != null) { Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners(); if (result != null) {     return result; } // If result is null, the existing retriever is not fully populated yet by another thread. // Proceed like caching wasn't possible for this current local attempt.    }    return retrieveApplicationListeners(eventType, sourceType, newRetriever);}

首先这个方法是在AbstractApplicationEventMulticaster类中,而这个类中有一个事件类型与事件监听器映射关系的检索缓存,然后咱们再看一下这个方法就明白了它的主要目的其实是为了缓存,缓存的Key是ResolvableType实例的事件类型事件源类型封装的ListenerCacheKey实例,缓存的Value是CachedListenerRetriever实例,其ListenerCacheKey和CachedListenerRetriever都是AbstractApplicationEventMulticaster的内部类,但注意这里有一个条件成立才会缓存:事件类型的类加载器、事件源类型的类加载器、以及AbstractApplicationEventMulticaster类的类加载器是同一个才行;

虽然上面那个方法没有进行匹配的实际操作,但缓存也是为了提高程序效率,咱们接着看retrieveApplicationListeners方法

private Collection<ApplicationListener<?>> retrieveApplicationListeners(    ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {    List<ApplicationListener<?>> allListeners = new ArrayList<>();    Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);    Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);    Set<ApplicationListener<?>> listeners;    Set<String> listenerBeans;    synchronized (this.defaultRetriever) { listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners); listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);    }    // Add programmatically registered listeners, including ones coming    // from ApplicationListenerDetector (singleton beans and inner beans).    for (ApplicationListener<?> listener : listeners) { if (supportsEvent(listener, eventType, sourceType)) {     if (retriever != null) {  filteredListeners.add(listener);     }     allListeners.add(listener); }    }    // Add listeners by bean name, potentially overlapping with programmatically    // registered listeners above - but here potentially with additional metadata.    if (!listenerBeans.isEmpty()) { ConfigurableBeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : listenerBeans) {     try {  if (supportsEvent(beanFactory, listenerBeanName, eventType)) {      ApplicationListener<?> listener =   beanFactory.getBean(listenerBeanName, ApplicationListener.class);      if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {   if (retriever != null) {if (beanFactory.isSingleton(listenerBeanName)) {    filteredListeners.add(listener);}else {    filteredListenerBeans.add(listenerBeanName);}   }   allListeners.add(listener);      }  }  else {      // Remove non-matching listeners that originally came from      // ApplicationListenerDetector, possibly ruled out by additional      // BeanDefinition metadata (e.g. factory method generics) above.      Object listener = beanFactory.getSingleton(listenerBeanName);      if (retriever != null) {   filteredListeners.remove(listener);      }      allListeners.remove(listener);  }     }     catch (NoSuchBeanDefinitionException ex) {  // Singleton listener instance (without backing bean definition) disappeared -  // probably in the middle of the destruction phase     } }    }    AnnotationAwareOrderComparator.sort(allListeners);    if (retriever != null) { if (filteredListenerBeans.isEmpty()) {     retriever.applicationListeners = new LinkedHashSet<>(allListeners);     retriever.applicationListenerBeans = filteredListenerBeans; } else {     retriever.applicationListeners = filteredListeners;     retriever.applicationListenerBeans = filteredListenerBeans; }    }    return allListeners;}

咱们可以忽略前面几行代码,因为都是一些局部变量的定义,唯一需要注意的是listeners和listenerBeans变量是通过defaultRetriever属性创建的新的变量,listeners是ApplicationListener实例集合,listenerBeans是ApplicationListener实例BeanName集合;

然后对listeners实例集合进行遍历,逐个判断ApplicationListener实例是否支持该事件,这里咱们需要看一下supportsEvent方法他是怎么去判断是否支持的

protected boolean supportsEvent(    ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {    GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?      (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));    return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));}

其实这个方法也能简单,就是判断应用监听器是否实现了GenericApplicationListener接口,如果是则直接调用该接口的supportsEventType方法supportsSourceType方法,来判断该事件类型和事件源类型是否支持,如果不是则将应用监听器包装为GenericApplicationListenerAdapter适配器类(它实现了GenericApplicationListener接口),再去调用刚刚那两个方法去进行判断;需要注意的是这里的条件是必须同时满足;

好了,咱们回过头去继续分析AbstractApplicationEventMulticaster类的retrieveApplicationListeners方法,刚刚对listeners实例进行了遍历,接下来它判断了listenerBeans监听器BeanName集合是否为空,如果不是则对其进行遍历,然后通过注入的beanFactory实例去获取BeanName的类型以及实例,同样进行判断是否支持该事件类型和事件源类型;

当对上面的listeners和listenerBeans都遍历操作完成后,此时支持该事件的应用监听器实例(或者BeanName)已添加到了方法开头定义的变量中,并返回所有支持的该事件的应用监听器,返回之前对其进行了排序;

至此,SimpleApplicationEventMulticaster事件广播器对事件的广播已分析完成,这块知识非常重要,在接下来的启动过程中还会再次使用它的;

4. ConfigurableEnvironment实例的创建与事件发布

接下来咱们接着SpringApplication的run方法进行分析,在第2点中咱们分析了该方法的前16行代码,咱们继续来看看第810行代码干了些什么?代码如下:

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);

首先这里的第一行代码内容比较简单,就是把启动应用的main方法的args参数(命令行参数)进行解析,然后封装到DefaultApplicationArguments类中,解析后的命令行参数类型为CommandLineArgs,解析工具为SimpleCommandLineArgsParser

然后我们接着看第二行代码的方法内容:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {    // Create and configure the environment    ConfigurableEnvironment environment = getOrCreateEnvironment();    configureEnvironment(environment, applicationArguments.getSourceArgs());    ConfigurationPropertySources.attach(environment);    listeners.environmentPrepared(environment);    bindToSpringApplication(environment);    if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,    deduceEnvironmentClass());    }    ConfigurationPropertySources.attach(environment);    return environment;}

通过方法名称可以判断出该方法为准备环境,那么这个环境实例是怎么被初始化的呢,咱们来一步步进行分析;

首先它调用了一个getOrCreateEnvironment方法去获取或者创建一个环境实例,如下:

private ConfigurableEnvironment getOrCreateEnvironment() {    if (this.environment != null) { return this.environment;    }    switch (this.webApplicationType) { case SERVLET:     return new StandardServletEnvironment(); case REACTIVE:     return new StandardReactiveWebEnvironment(); default:     return new StandardEnvironment();    }}

这个方法的逻辑就是先判断SpringApplication实例中的environment属性是否为空,如果不为空则直接返回,否则根据webApplicationType属性去创建对应的环境实例进行返回,友情提醒:关于SpringApplication类中的属性在本文第1点中已经有分析了;鉴于我们日常使用的都是Web Servlet应用,所以这里创建的环境实例类型为StandardServletEnvironment;

环境实例得到后,则开始对其进行配置,方法如下:

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {    if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService);    }    configurePropertySources(environment, args);    configureProfiles(environment, args);}
  • 第一件事情:

    根据SpringApplication实例的addConversionService属性去判断是否添加转换器服务实例,一般情况下该属性为true,所以都进行添加了的;

  • 第二件事情:

    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 = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; 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)); }    }}

    判断SpringApplication实例的defaultProperties属性是否不为空,如果不为空则添加到环境实例中;再根据addCommandLineProperties属性的值,如果该属性为true且命令行参数(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));}

    其实就是设置环境激活的配置文件,将环境中激活的配置文件和SpringApplication实例中additionalProfiles属性设置的配置文件合并到一起,然后重新注入到环境实例中;

以上已经对环境实例进行了配置,而接下来的ConfigurationPropertySources.attach静态方法只是对环境中属性源进行了包装,创建了一个SpringConfigurationPropertySources实例,并以configurationProperties为名称重新注入到了环境属性中;

接下来这行代码listeners.environmentPrepared(environment);咱们就比较熟悉了,和本文第2点一样,通过事件发布机制通知所有应用监听器环境已经准备好了,具体实现逻辑是和第2点一样的,就不再重复分析了;

知识扩展:

在监听环境准备事件的所有应用监听器中,有一个比较重要的应用监听器:ConfigFileApplicationListener,因为它实现了EnvironmentPostProcessor接口所以同时也是环境的后置处理器;当环境准备事件被发布时,会触发该监听器进行执行,而执行的逻辑为找到所有环境后缀处理器,进行一一后置处理,所以该监听器同时作为环境后置处理器会被再次执行,而作为后置处理器的执行逻辑为从 spring.config.location 属 性 配 置 的 目 录 去 找 到 {spring.config.location}属性配置的目录去找到 spring.config.location{spring.config.name}属性名称的文件,如果 spring.config.location 或 者 {spring.config.location}或者 spring.config.location{spring.config.name}为null,则依次从:file:./config/、file:./config/*/、file:./、classpath:/config/、classpath:/目录去加载名称为application的properties或者yml文件,来作为应用的配置文件进行启动应用程序!

到此为止,所有监听了环境准备事件的应用监听器已经执行完成了,接下来咱们进行看看下一行代码调用的方法所执行的内容:

protected void bindToSpringApplication(ConfigurableEnvironment environment) {    try { Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));    }    catch (Exception ex) { throw new IllegalStateException("Cannot bind to SpringApplication", ex);    }}

这个方法的逻辑是将以spring.main前缀的对应属性绑定到SpringApplication实例中;

然后接下来的 if 语句是将环境实例对象转换成对应的web应用类型所对应的环境类型;

最后的ConfigurationPropertySources.attach方法是将环境实例中的configurationProperties属性给重新根据环境实例再附属一下,都不是什么重要的逻辑;

最后我们来看看第三行代码的方法内容:

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {    if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) { Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE); System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());    }}

其实就是通过环境实例向系统属性设置了spring.beaninfo.ignore的值;

5. Banner的打印

当环境实例准备好以后,就开始了我们熟悉的Banner的打印;咱们接着来分析:

private Banner printBanner(ConfigurableEnvironment environment) {    if (this.bannerMode == Banner.Mode.OFF) { return null;    }    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(null);    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);    if (this.bannerMode == Mode.LOG) { return bannerPrinter.print(environment, this.mainApplicationClass, logger);    }    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);}

这是打印Banner的方法,逻辑比较简单,先是判断SpringApplication实例的Banner打印模式属性bannerMode是否为OFF值,如果是则不打印了,如果不是则表示要打印Banner,然后继续通过SpringApplication实例的resourceLoader资源加载器属性和banner属性创建了变量名为bannerPrinter的SpringApplicationBannerPrinter实例,最后再通过bannerMode属性判断是通过日志组件打印还是控制台打印,最终都会执行到SpringApplicationBannerPrinter实例的两个print重载方法的其中一个中;

关于Banner的选择,内部是这样的顺序:

  1. SpringApplication实例中的banner属性在不为null的情况下拥有最高优先权;
  2. 然后是环境实例中spring.banner.image.location属性指定的图像资源作为Banner;
  3. 如果第2点的属性值为空,则依次从classpath目录下查找banner.gifbanner.jpgbanner.png图像资源作为Banner;
  4. 再然后是环境实例中spring.banner.location属性指定的文本文件作为Banner;
  5. 如果第4点的的属性值为空,则在classpath目录下查找banner.txt文件作为Banner;
  6. 最后如果以上Banner资源都未配置,或者配置的资源不存在,则使用Spring Boot默认提供的Banner;

Banner资源获取到以后,则根据调用的print方法的不同而进行不同方式的打印;

最终将Banner资源实例,以及应用启动类Class实例封装成PrintedBanner类型返回到run方法中;

6. ConfigurableApplicationContext实例的创建与事件发布

以上,关于Spring Boot的准备工作已经完成得差不多了,接下来就是开展Spring的核心实例的创建了;

我们接着分析run方法的第12~15行代码:

context = createApplicationContext();prepareContext(context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);

咱们都知道IOC是Spring的核心之一,那么在这里就开始初始化IOC容器,整个过程分为:创建容器、准备容器、刷新容器、刷新后处理(这个方法为空实现);咱们开始一个一个分析;

  1. 创建容器

    protected ConfigurableApplicationContext createApplicationContext() {    Class<?> contextClass = this.applicationContextClass;    if (contextClass == null) { try {     switch (this.webApplicationType) {  case SERVLET:      contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);      break;  case REACTIVE:      contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);      break;  default:      contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);     } } catch (ClassNotFoundException ex) {     throw new IllegalStateException(  "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex); }    }    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);}

    以上为run方法第12行代码所调用的方法,目的就是根据Web应用类型参数webApplicationType去创建对应的IOC容器实例;因为咱们通常使用的都是Servlet应用类型,所以咱们以Servlet所对应的IOC容器org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext去分析后面的代码流程;

  2. 准备容器

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {    context.setEnvironment(environment);    postProcessApplicationContext(context);    applyInitializers(context);    listeners.contextPrepared(context);    if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context);    }    // Add boot specific singleton beans    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());    }    // Load the sources    Set<Object> sources = getAllSources();    Assert.notEmpty(sources, "Sources must not be empty");    load(context, sources.toArray(new Object[0]));    listeners.contextLoaded(context);}

    以上为run方法第13行代码所调用的方法,具体逻辑咱们来一点一点分析;

    第一步:将第4点中创建的环境实例注入到IOC容器中,在容器内部同时会注入到AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner实例中去;

    第二步:对IOC容器进行后缀处理,具体逻辑为将SpringApplication实例中不为空的beanNameGeneratorresourceLoader以及转换器服务属性实例注入到IOC容器中,前者是注入到一级缓存中,后两者是设置IOC容器的属性;

    第三步:遍历SpringApplication实例的IOC容器初始化器集(在文本第1点中实例化的),然后将当前的IOC容器一一传递给这些初始化器进行处理;

    • DelegatingApplicationContextInitializer:IOC容器初始化委托器,委托给context.initializer.classes环境属性配置的类,然后逐个进行初始化;
    • SharedMetadataReaderFactoryContextInitializer:向IOC容器中添加一个Bean工厂后置处理器,类型为org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor
    • ContextIdApplicationContextInitializer:设置IOC容器的ID,ID以环境中spring.application.name属性值为准,如果属性值不存在则默认为application
    • ConfigurationWarningsApplicationContextInitializer:向IOC容器中添加一个Bean工厂后置处理器,类型为org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer.ConfigurationWarningsPostProcessor
    • RSocketPortInfoApplicationContextInitializer:向IOC容器中添加一个应用监听器,类型为org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer.Listener
    • ServerPortInfoApplicationContextInitializer:向IOC容器中添加一个应用监听器,类型为自身,因为该类实现了ApplicationListener接口;
    • ConditionEvaluationReportLoggingListener:向IOC容器中添加一个应用监听器,类型为org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener.ConditionEvaluationReportListener,用于打印条件评估报告;

    第四步:发布IOC容器初始化事件,逻辑与之前的事件发布一致;

    第五步:这个if语句只是打印了应用启动的相关日志信息,比如启动类、主机名称、PID、和激活的配置文件等等;

    第六步:向IOC容器中注入了单例的springApplicationArguments(命令行参数实例)、springBootBanner(Banner实例),以及设置allowBeanDefinitionOverriding(是否允许BeanDefinition被覆盖)属性,和根据lazyInitialization属性的值判断是否注入LazyInitializationBeanFactoryPostProcessor延迟初始化Bean工厂后缀处理器;

    第七步:将SpringApplication实例中的primarySourcessources资源属性注册到IOC容器中;

    第八步:将SpringApplication实例中的应用监听器注入到IOC容器中,同时发布IOC容器已准备事件;

  3. 刷新容器

    首先在容器内部向JVM注册了一个线程名称为SpringContextShutdownHook的应用程序停止的回调钩子,用于执行容器的消耗工作;

    然后开始刷新IOC容器,整个执行过程如下:

    第一步:准备刷新IOC容器,包括设置容器激活标识、校验环境必需的属性配置、初始化earlyApplicationListenersearlyApplicationEvents属性;

    第二步:获取最新的Bean工厂,并设置Bean工厂的serializationId属性;

    第三步:准备Bean工厂,其实就是对BeanFactory属性进行填充,同时注入一些单例Bean,如:环境实例、系统属性、系统环境;

    第四步:后置处理Bean工厂,也是一些属性的填充与注册;

    第五步:调用Bean工厂后置处理器,并进行执行,主要是BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor接口实例的执行,我们熟悉的工作“Bean的扫描”就是在这里执行的;

    第六步:注册Bean实例的后置处理器;

    第七步:初始化IOC容器消息源;

    第八步:初始化应用事件广播器;

    第九步:留给子类初始化其他实例,在Spring Boot中Web服务就是在这里初始化的,比如:Tomcat;

    第十步:将IOC容器中的应用监听器以及Bean工厂的应用监听器注入到第八步的广播器中;

    第十一步:完成Bean的初始化以及依赖注入(不包括懒加载的Bean),包括代理对象的创建等等;

    第十二步:完成刷新过程,清空一些缓存,并发布ContextRefreshedEvent事件;

  4. 刷新后处理

    空实现,没有任何逻辑;

7. 启动成功日志的打印

至此,Spring Boot应用已成功启动,而下面的这两行代码就是我们非常熟悉的应用启动成功标识日志打印;

if (this.logStartupInfo) {    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}

日志内容如下:

Started SpringActuatorBootApplication in 2.428 seconds (JVM running for 4.836)

8. ApplicationStartedEvent与ApplicationReadyEvent事件的发布

最后,还有三行代码没有进行分析,咱们一个一个来;

代码一:

listeners.started(context);
void started(ConfigurableApplicationContext context) {    for (SpringApplicationRunListener listener : this.listeners) { listener.started(context);    }}
@Overridepublic void started(ConfigurableApplicationContext context) {    context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));    AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);}

这行代码做了两件事情,一是发布ApplicationStartedEvent事件,二是更新应用程序的LivenessState状态为CORRECT

代码二:

callRunners(context, applicationArguments);
private void callRunners(ApplicationContext context, ApplicationArguments args) {    List<Object> runners = new ArrayList<>();    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());    AnnotationAwareOrderComparator.sort(runners);    for (Object runner : new LinkedHashSet<>(runners)) { if (runner instanceof ApplicationRunner) {     callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) {     callRunner((CommandLineRunner) runner, args); }    }}

这行代码就是对ApplicationRunnerCommandLineRunnerBean实例进行回调;

代码三:

listeners.running(context);
void running(ConfigurableApplicationContext context) {    for (SpringApplicationRunListener listener : this.listeners) { listener.running(context);    }}
@Overridepublic void running(ConfigurableApplicationContext context) {    context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));    AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);}

这行代码和代码一逻辑一致,共做了两件事情,一是发布ApplicationReadyEvent事件,二是更新应用程序的ReadinessState状态为ACCEPTING_TRAFFIC

但需要特别注意的是ApplicationReadyEvent事件发布后,会对Spring MVC核心实例DispatcherServlet进行初始化;