springboot源码分析之SpringApplication

想必大家对springbootApplication很熟悉,它是我们springboot的项目的一个入口,来看一段大家都熟知的代码:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AppApplication {

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

}

上面这是我创建的一个springboot项目,这段代码就是我们项目的入口,看似一段很简单的代码,其真正的作用在于注解SpringBootApplication身上,既然我们知道了入口,那么我们来看看:

  • 注解SpringBootApplication在此处首先表明是一个springboot应用,通过该注解可以开启自动装配的功能.
  • SpringApplication.run(...)是用来启动该应用.

所以接下来我们来看看springboot项目的启动的过程,关于注解SpringBootApplication我们专门来说.

SpringApplgication

SpringApplgication位于org.springframework.boot.SpringApplication包下,是spring应用的启动类,来看一下官方是如何说的:

Class that can be used to bootstrap and launch a Spring application from a Java main method. By default class will perform the following steps to bootstrap your application

在实际的开发中,我们应该用到的是它的static方法,来看代码:

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);
}
public static void main(String[] args) throws Exception {
    SpringApplication.run(new Class<?>[0], args);
}
  • 简单的三个静态方法,我们可以从中发现,第二个和第三个静态方法最后还是调用第一个静态方法
  • 首先是new SpringApplication对象.
  • 接着是调用SpringApplication#run(Class<?> primarySource, String... args)方法,真正的运行我们的spring应用

既然知道了上面代码的执行顺序,我们进行一步一步的学习,首先我们来看构造SpringApplication的过程,代码如下:

//用来保存javaConfig类的数组
private Set<Class<?>> primarySources;
//我们启动类的类型
private Class<?> mainApplicationClass;
//资源加载器
private ResourceLoader resourceLoader;
//针对于web类型的应用
private WebApplicationType webApplicationType;
//用来保存ApplicationContextInitializer类型的集合
private List<ApplicationContextInitializer<?>> initializers;
//用来保存ApplicationListener的集合
private List<ApplicationListener<?>> listeners;

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();
    //初始化ApplicationContextInitializer过程
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //初始化ApplicationListener过程
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

简单的来看下我们上面提到的这些属性:

  • primarySources:是用来保存javaConfig类的,也就是我们本篇开头示例中的AppApplication类.
  • mainApplicationClass是通过调用 #deduceMainApplicationClass() 方法,来判断我们调用的是哪个main方法.
  • resourceLoader也就是我们的资源加载器,我们在之前的spring文章中的spring容器实现之资源加载我们详细的讲解了加载资源的过程,感兴趣的可以去看看.
  • webApplicationType其主要的作用是通过调用WebApplicationType#deduceFromClasspath()方法来判断我们的web类型.
  • initializers 是ApplicationContextInitializer集合,详解后面来说.
  • listeners是用来ApplicationListener的集合,详解见后面.
deduceMainApplicationClass()
private Class<?> deduceMainApplicationClass() {
    try {
        //获取当前StackTraceElement类型的数组
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        //遍历处理
        for (StackTraceElement stackTraceElement : stackTrace) {
            //判断哪个执行了main方法
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

该方法是我们属性mainApplicationClass的作用,位于SpringApplication.java中.

getSpringFactoriesInstances()

该方法的是啥哈,主要是针对于上面的属性initializers和listeners来实现的,不同的是一个获取ApplicationContextInitializer的对象集合,一个是ApplicationListener的对象集合,我们来看实现过程:

SpringApplication.java
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 classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    //加载指定的类型的类名集合,从META-INF/spring.factories下加载
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //创建实例对象
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    //给创建的实例对象排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

几行代码,却做了很多事,简单的来看一下:

  • 首先是调用#getClassLoader()来获取类加载器.
  • 然后是调用SpringFactoriesLoader#loadFactoryNames(...)方法去加载指定了类型的类名集合,在META-INF/spring.factories路径下去加载.
  • 接着是通过#createSpringFactoriesInstances(...)来进行实例的创建过程
  • 最后对刚创建的实例们进行排序

这就是上面代码的为我们所做的事,其中在第二步中,可能我们对META-INF/spring.factories路径下的东东不太明白,其实就是以key-value的形式存在,关于具体的加载过程,后续我们来说,我们来看看创建实例的过程:

// SpringApplication.java

/**
 * 创建对象的集合
 *
 * @param type 父类
 * @param parameterTypes 构造方法的参数类型
 * @param classLoader 类加载器
 * @param args 参数
 * @param names 类名的集合
 * @param <T> 泛型
 * @return 对象的数组
 */
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
        ClassLoader classLoader, Object[] args, Set<String> names) {
    //通过names集合的大小构建一个新的同样大小的集合
    List<T> instances = new ArrayList<>(names.size());
    //遍历处理
    for (String name : names) {
        try {
            //利用反射机制,获取name对应的具体类
            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;
}

上述创建实例的过程 其实也并不复杂,还是利用反射的机制来完成实例的创建过程,看完了第一个核心过程,我们来看看第二个过程就是调用方法#run(...),方法明确表示就是用来启动我们的spring的运用的,接下来我们来看看具体的实现过程:

run方法
#SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
    //1.创建StopWatch实例
    //调用它的start方法启动StopWatch来计时run方法的执行cd
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    //2.配置Headless属性
    configureHeadlessProperty();
    //通过getRunListeners来获取所有的SpringApplicationRunListeners实例,同时开启监听操作
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        //3.通过参数args来创建ApplicationArguments对象
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //4.预加载所有的配置信息,包括environment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

        configureIgnoreBeanInfo(environment);
        //5.打印Banner图样
        Banner printedBanner = printBanner(environment);
        //6.创建上下文
        context = createApplicationContext();
        //7.创建异常报告器
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        //8.初始化上下文(通过调用别的方法)前的准备
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        //9.执行初始化操作
        refreshContext(context);
        //10.后置处理的操作,默认是空实现
        afterRefresh(context, applicationArguments);
        //11.停止执行的运行时间
        stopWatch.stop();
        //12.springboot的启动日志打印过程
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        //13.通知相应的监听器spring容器启动完成
        listeners.started(context);
        //14.调用 ApplicationRunner或CommandLineRunner的运行方法
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        //若在此过程中发生异常,直接抛IllegalStateException
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        //15.再次通知相应的监听器spring容器处于运行的状态
        listeners.running(context);
    }
    catch (Throwable ex) {
        //如果在此过程中发生异常,直接抛IllegalStateException
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

额,代码有点长,我们简单的来看一下每一步的过程:

  • 在1.处,首先是创建StopWatch实例同时调用start方法启动它,其目的是用来监控run方法运行的时长.
  • 在2.处,通过调用#configureHeadlessProperty()方法来配置Headless属性
private void configureHeadlessProperty() {
    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
            System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

好像该属性和AWT有关,感觉对我们来说作用不大,哈哈哈...,不是很清楚.

  • 在2.1.处,通过调用#getRunListeners()方法来获取SpringApplicationRunListeners集合,同时开启监听操作,代码如下:
//SpringApplication.java
private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

我们可以通过Dbug发现我们此时的listeners数组里的变量,如图所示:

方法.png

listeners中仅有一个变量实例就是我们图中的SpringApplicationRunListener类型.关于它后面来说,这里知道即可.

  • 在3.处通过我们传入的参数args来构建ApplicationArguments对象,作为下一步的必要参数.
  • 在4.处通过调用#prepareEnvironment(...)来预加载所有的配置信息,包括environment.
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    //1.通过调用#getOrCreateEnvironment()来创建ConfigurableEnvironment实例
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    //2.进行相关的配置
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    //3.设置environment的属性源
    ConfigurationPropertySources.attach(environment);
    //4.通知SpringApplicationRunListener当前环境已就绪
    listeners.environmentPrepared(environment);
    //5.将environment绑定到SpringApplication上
    bindToSpringApplication(environment);
    //6.非自定义的环境,通过条件去转换
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    //7.将environment添加到PropertySource中
    ConfigurationPropertySources.attach(environment);
    return environment;
}

好像代码不是很多,逻辑挺多的,简单的来看一下:

  • 在4.1.处,主要是构建ConfigurableEnvironment对象的过程,来看看如何构建的
//SpringApplication.java
private ConfigurableEnvironment environment;
//针对于web类型的应用
private WebApplicationType webApplicationType;
private ConfigurableEnvironment getOrCreateEnvironment() {
    //如果有,直接返回即可.
    if (this.environment != null) {
        return this.environment;
    }
    //不存在的话,通过webApplicationType来创建
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}

可以看到的是:

  • 如果当前存在environment的话,直接用它
  • 在不存在的情况下,通过webApplicationType来创建不同类型的ConfigurableEnvironment对象
  • 在4.2.处,通过#configureEnvironment(...)方法进行环境的配置过程.
//是否添加共享的ConversionService 
  private boolean addConversionService = true;
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    //设置environment的ConversionService属性
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    //1.1.添加environment的PropertySource属性源
    configurePropertySources(environment, args);
    //1.2.配置environment的activeProfiles属性
    configureProfiles(environment, args);
}

该方法其主要的作用是设置environment的一些属性其中包括:

  • 在4.1.1.处,通过方法#configurePropertySources(...)来设置environment的 PropertySource属性
//SpringApplication.java
/**jvm参数*/
private boolean addCommandLineProperties = true;

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    //获取Mu对象tablePropertySources
    MutablePropertySources sources = environment.getPropertySources();
    //设置默认的PropertySources
    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));
        }
    }
}

从代码中可以看到的是作为PropertySources参数的分别有jvm参数以及默认的propertySources.

  • 在4.1.2处通过方法#configureProfiles(...)来设置切换属性
private Set<String> additionalProfiles = new HashSet<>();
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    //确保已经被初始化
    environment.getActiveProfiles(); // ensure they are initialized
    // But these ones should go first (last wins in a property key clash)
    Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    //设置ActiveProfiles
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

代码简单,这里涉及到了springboot是如何进行Profiles的切换过程,后续来说.

  • 在4.3处,主要是添加属性源的过程.
//ConfigurationPropertySource.java
private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";
public static void attach(Environment environment) {
    Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
    MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
    PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
    if (attached != null && attached.getSource() != sources) {
        sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
        attached = null;
    }
    if (attached == null) {
        sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
                new SpringConfigurationPropertySources(sources)));
    }
}

从代码中我们可以看到的通过获取到的attached来区分,存在的话从sources中移除,不存在的话,加入默认的即可,这里不多说了,接着看:

  • 在4.4处通过方法#environmentPrepared(...)来通知SpringApplicationRunListener,其主要的目的是告诉它当前环境已经准备就绪.

  • 在4.5.处,通过方法#bindToSpringApplication(...)将当前environment绑定在当前SpringApplication上.

  • 在4.6.处,如果非自定义 environment ,则根据条件转换.默认情况下isCustomEnvironment 为 false ,所以会执行这块逻辑。但是,一般情况下,返回的还是 environment 自身,所以可以无视这块逻辑先.

  • 在4.7.处.跟4.3处一样,这里就不多扯了.

  • 在5处,通过调用#printBanner(...)来打印springboot启动的Banner图样,相信这个大家都知道,就不多扯了...

  • 在6.处,通过方法#createApplicationContext()创建spring容器.后面来讲

  • 在7.处,通过 #getSpringFactoriesInstances(Class<T> type) 方法,进行获取SpringBootExceptionReporter 类型的对象数组,SpringBootExceptionReporter ,记录启动过程中的异常信息.

通过dbug我们发现,在此处的exceptionReporters数组中有一个变量,如图:

微信截图_20190829214623.png
  • 在8处,通过调用#prepareContext(...)方法来初始化上下文环境,这里主要是通过调用别动初始化方法来完成的,详解见后面.
  • 在9处,调用#refreshContext(ConfigurableApplicationContext context)方法,启动(刷新) Spring 容器,详解见后面.
  • 在10处,调用#afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) 方法执,行 Spring 容器的初始化的后置处理,默认实现为空,代码如下:
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
  • 在11处,通过调用stopWatch#stop()来停止统计run方法的执行时长.
  • 在12处,打印 Spring Boot 启动时的时长日志,如下图所示:
微信截图_20190829221253.png
  • 在13处,调用SpringApplicationRunListeners#started(ConfigurableApplicationContext context) 方法,通知 SpringApplicationRunListener 的集合,Spring 容器启动完成,关于该方法的详解后面来说.
  • 在14.处,首先是调用# ApplicationRunner或CommandLineRunner的运行方法,若在此过程中发生异常,直接抛IllegalStateException
  • 在15处,通过SpringApplicationRunListeners#running(ConfigurableApplicationContext context) 方法,通知 SpringApplicationRunListener 的数组,Spring 容器运行中,如果在此过程中发生异常,直接抛IllegalStateException

接下来我们分别来看看上述我们遗留的一些方法的详解,首先我们来看看6处遗留的创建spring容器的过程来看是如何实现的.

createApplicationContext()方法
//SpringApplication.java
private Class<? extends ConfigurableApplicationContext> applicationContextClass;
/**
 * The class name of application context that will be used by default for web
 * environments.
 */
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
        + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
/**
 * The class name of application context that will be used by default for reactive web
 * environments.
 */
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
        + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

/**
 * The class name of application context that will be used by default for non-web
 * environments.
 */
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
    //通过webApplicationType类型类来完成ApplicationContext的创建过程
    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);
        }
    }
    //这里表示contextClass不为null,则直接通过反射的方法去创建
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

从上述代码可以看出,创建spring容器的过程并不复杂,还是根据webApplicationType来判断进行创建不同的ApplicationContext对象,接着我们来看在8处遗留的通过方法#prepareContext(....)来初始化spring容器

prepareContext()
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    //设置context的environment的环境
    context.setEnvironment(environment);
    //1.设置一些跟当前ApplicationContext有关的属性
    postProcessApplicationContext(context);
    //2.初始化ApplicationContextInitializer
    applyInitializers(context);
    //3.通知SpringApplicationRunListener的数组,Spring容器准备完成
    listeners.contextPrepared(context);
    //4.打印启动日志
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    //5.获取单例ConfigurableListableBeanFactory
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    //将applicationArguments注册到beanFactory中
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    //6.加载资源文件主要是(beanDefinitions)
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    //7.通知 SpringApplicationRunListener的数组,Spring容器加载完成
    listeners.contextLoaded(context);
}

我们可以看到的是该方法主要的作用是针对applicationContext对象的属性做了一些初始化的操作.我们简单的来看下过程:

  • 首先是对environment的设置
  • 在1处,通过调用#postProcessApplicationContext(ConfigurableApplicationContext context)方法,设置一些跟当前ApplicationContext有关的属性,代码如下:
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    if (this.beanNameGenerator != null) {
        context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                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());
    }
}
  • 在2处,通过调用 #applyInitializers(ConfigurableApplicationContext context) 方法,初始化 ApplicationContextInitializer,代码如下:
/**
 * Apply any {@link ApplicationContextInitializer}s to the context before it is
 * refreshed.
 * @param context the configured ApplicationContext (not refreshed yet)
 * @see ConfigurableApplicationContext#refresh()
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
    //遍历处理ApplicationContextInitializer数组
    for (ApplicationContextInitializer initializer : getInitializers()) {
        //非空的泛型校验
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        //初始化context
        initializer.initialize(context);
    }
}
  • 在3处,通过SpringApplicationRunListeners#contextPrepared(ConfigurableApplicationContext context) 方法,通知 SpringApplicationRunListener 的数组,Spring 容器准备完成
  • 在4处,打印springboot启动时的日志
  • 在5处,只要是获取单例的ConfigurableListableBeanFactory实例并设置相关属性
  • 在6处,主要是加载资源文件(beanDefinition),来看代码:
protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
        logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
    //<1>创建BeanDefinitionLoader实例
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
        //2.设置loader的BeanNameGenerator属性
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        //设置loader的ResourceLoader属性
        loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
        //设置loader的Environment属性
        loader.setEnvironment(this.environment);
    }
    //<3>执行加载操作
    loader.load();
}

我们可以看到的是在<1>处,首先是通过方法#getBeanDefinitionRegistry(...)来构建BeanDefinitionRegistry对象,我们来看代码:

private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
    if (context instanceof BeanDefinitionRegistry) {
        return (BeanDefinitionRegistry) context;
    }
    if (context instanceof AbstractApplicationContext) {
        return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
    }
    throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}

代码简单就不多扯了,接着看:

  • 同样是在<1>处,等创建完BeanDefinitionRegistry对象时,作为构建BeanDefinitionLoader对象的参数进行创建过程,其中调用的是#createBeanDefinitionLoader(...)方法,额,大家都懂,由于篇幅太长我们后续来说吧.
  • 在<2>处,通过条件的判断对BeanDefinitionLoader对象分别进行属性BeanNameGenerator 和ResourceLoader以及Environment的设置过程.
  • 在<3>处才是执行加载的过程,感兴趣的可以去看看,到这里我就为止了...
  • 在7处,调用 SpringApplicationRunListeners#contextLoaded(ConfigurableApplicationContext context) 方法,通知 SpringApplicationRunListener 的数组,Spring 容器加载完成

接着我们来看9处遗留的关于方法#refreshContext(...)启动spring容器的过程,直接看代码:

//是否开启对ShutdownHook的注册
private boolean registerShutdownHook = true;
private void refreshContext(ConfigurableApplicationContext context) {
    //1.开启spring容器
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            //2.向context中注册ShutdownHook
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

代码结构清晰简单,其主要的逻辑处理还是通过调用别的方法来完成,简单的来看一下:

  • 在1处,通过调用#refresh(ApplicationContext applicationContext)方法来开启spring容器.代码如下:
/**
 * Refresh the underlying {@link ApplicationContext}.
 * @param applicationContext the application context to refresh
 */
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}
  • 在2.处,调用 ConfigurableApplicationContext#registerShutdownHook() 方法,注册 ShutdownHook ,该类主要用于 Spring 应用的关闭时,销毁相应的 Bean 们

看完了该方法的作用我们来看看在14.处我们遗留的callRunners(...)的详解.

callRunners

该方法主要的作用是通过调用ApplicationRunners或者是CommandLineRunner方法,我们来看看代码:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    //<1>.用来保存所有的runners
    List<Object> runners = new ArrayList<>();
    //<1.1>将从context中获取到的ApplicationRunner的bean保存到runners中
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    //<1.2>将从context中获取到的CommandLineRunner的bean保存到runners中
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    //<1.3>对runners进行排序
    AnnotationAwareOrderComparator.sort(runners);
    //<1.4>遍历进行执行相应的逻辑
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

方法简单明了,我们简单的总结一下:

  • 在<1>处,创建一个用来保存runners类型bean的数组.
  • 在<1.4>处,遍历runners数组,执行逻辑.我们来看代码:
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
    try {
        (runner).run(args);
    }
    catch (Exception ex) {
        throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
    }
}

private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
    try {
        (runner).run(args.getSourceArgs());
    }
    catch (Exception ex) {
        throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
    }
}

在上述代码中,用到了ApplicationRunner接口和CommandLineRunner接口,这里就不多说了.

SpringApplicationRunListeners

该类位于org.springframework.boot.SpringApplicationRunListeners包下,是对SpringApplicationRunListener的封装,我们来看代码:

''''''
/**
 * A collection of {@link SpringApplicationRunListener}.
 *
 * @author Phillip Webb
 */
class SpringApplicationRunListeners {

private final Log log;

private final List<SpringApplicationRunListener> listeners;

SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
    this.log = log;
    this.listeners = new ArrayList<>(listeners);
}

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

public void environmentPrepared(ConfigurableEnvironment environment) {

    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}

public void contextPrepared(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.contextPrepared(context);
    }
}

public void contextLoaded(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.contextLoaded(context);
    }
}

public void started(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.started(context);
    }
}

public void running(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.running(context);
    }
}

public void failed(ConfigurableApplicationContext context, Throwable exception) {
    for (SpringApplicationRunListener listener : this.listeners) {
        callFailedListener(listener, context, exception);
    }
}

private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
        Throwable exception) {
    try {
        listener.failed(context, exception);
    }
    catch (Throwable ex) {
        if (exception == null) {
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        if (this.log.isDebugEnabled()) {
            this.log.error("Error handling failed", ex);
        }
        else {
            String message = ex.getMessage();
            message = (message != null) ? message : "no error message";
            this.log.warn("Error handling failed (" + message + ")");
        }
    }
}

我们来看一下该接口方法的定义:

''''
public interface SpringApplicationRunListener {

/**
 * Called immediately when the run method has first started. Can be used for very
 * early initialization.
 */
void starting();

/**
 * Called once the environment has been prepared, but before the
 * {@link ApplicationContext} has been created.
 * @param environment the environment
 */
void environmentPrepared(ConfigurableEnvironment environment);

/**
 * Called once the {@link ApplicationContext} has been created and prepared, but
 * before sources have been loaded.
 * @param context the application context
 */
void contextPrepared(ConfigurableApplicationContext context);

/**
 * Called once the application context has been loaded but before it has been
 * refreshed.
 * @param context the application context
 */
void contextLoaded(ConfigurableApplicationContext context);

/**
 * The context has been refreshed and the application has started but
 * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
 * ApplicationRunners} have not been called.
 * @param context the application context.
 * @since 2.0.0
 */
void started(ConfigurableApplicationContext context);

/**
 * Called immediately before the run method finishes, when the application context has
 * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
 * {@link ApplicationRunner ApplicationRunners} have been called.
 * @param context the application context.
 * @since 2.0.0
 */
void running(ConfigurableApplicationContext context);

/**
 * Called when a failure occurs when running the application.
 * @param context the application context or {@code null} if a failure occurred before
 * the context was created
 * @param exception the failure
 * @since 2.0.0
 */
void failed(ConfigurableApplicationContext context, Throwable exception);

}

该接口里面的方法都有详细的解释,可以自己去看看,我们发现该接口唯一的实现类是EventPublishingRunListener

EventPublishingRunListener类
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

/**
 * 当前的spring应用
 */
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);
    }
}

@Override
public int getOrder() {
    return 0;
}

@Override
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster
            .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

@Override
public void contextPrepared(ConfigurableApplicationContext context) {
    this.initialMulticaster
            .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}

@Override
public void contextLoaded(ConfigurableApplicationContext context) {
    for (ApplicationListener<?> listener : this.application.getListeners()) {
        if (listener instanceof ApplicationContextAware) {
            ((ApplicationContextAware) listener).setApplicationContext(context);
        }
        context.addApplicationListener(listener);
    }
    this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

@Override
public void started(ConfigurableApplicationContext context) {
    context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}

@Override
public void running(ConfigurableApplicationContext context) {
    context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
}

@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
    ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
    if (context != null && context.isActive()) {
        // Listeners have been registered to the application context so we should
        // use it at this point if we can
        context.publishEvent(event);
    }
    else {
        // An inactive context may not have a multicaster so we use our multicaster to
        // call all of the context's listeners instead
        if (context instanceof AbstractApplicationContext) {
            for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
                    .getApplicationListeners()) {
                this.initialMulticaster.addApplicationListener(listener);
            }
        }
        this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
        this.initialMulticaster.multicastEvent(event);
    }
}

private static class LoggingErrorHandler implements ErrorHandler {

    private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);

    @Override
    public void handleError(Throwable throwable) {
        logger.warn("Error calling ApplicationEventListener", throwable);
    }

}

代码简单,可以自己看看,这里我们可以将EventPublishingRunListener认为是springApplication的一个事件转换器,关于它的详解我们后面来看....

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 2018年6月17日 姓名:潘红军 公司 :扬州市方圆建筑工程有限公司 【日精进打卡第145天】 南京第349期...
    5119a64ee3ab阅读 75评论 0 0
  • 上一章 我和老王在那间办公室手拉着手终于就我的升职问题达成了一致,这才喜笑颜开的又坐了下来。 老王又一本正经的强调...
    岛主王仙客阅读 283评论 0 1
  • 别人的想法 是飘忽不定的 他们想着和恋人幽会 想走大运或出大名 我总是想着麻烦 我的想法是稳重的 所以当麻烦来临时...
    五星连珠阅读 3,035评论 1 4
  • 文‖林翻飞 “小飞,你的快递!” 小飞抬起趴在桌子上的头。 “小红,我哪有什么快递,又不网购,最近也没人寄东西啊!...
    林翻飞阅读 517评论 1 1