(粗阅)springboot的初始化顺序

使用springboot也有1年了,对于Java开发者而言这确实是一个方便快捷的开发框架,但一值都没深入去了解过的,最近由于开发中出现了filter执行顺序问题导致系统报错(虽然通过设置setOrder解决了执行顺序问题),但觉得还是有必要去了解下springboot的启动初始化过程(下面是比较粗浅的过一遍启动流程,以后深入学习后会在更新 )

一、SpringApplication初始化

spring boot启动是从SpringApplication.run();方法开始的,这是SpringApplication的静态方法,那么先进入SpringApplication.class来看一下这个方法:
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);
}

这一步新建了一个SpringApplication的对象,然后再调用它的run方法,先去看下SpringApplication类的构造方法:
public SpringApplication(Class... primarySources) {
    this((ResourceLoader)null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = 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();
},

可以看到SpringApplication的构造入参有两种类型:一个是ResourceLoader,一个是Class对象或数组,可以看出来,到了这里primarySources参数里面只有启动类一个元素。
在SpringApplication的构造方法内主要完成了以下几个事情:
1、确定Web应用的类型。

我们看到WebApplicationType.deduceFromClasspath()方法是根据判断当前的类的类型来判断web应用的类型的:

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;
}
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        forName(className, classLoader);
        return true;
    } catch (IllegalAccessError var3) {
        throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + var3.getMessage(), var3);
    } catch (Throwable var4) {
        return false;
    }
}

这一块是比较容易看懂的(这个不说太细基本上),通过推断类路径这个方法,去判断你是属于哪种类型的web应用程序,基本上就是通过isPresent 方法中的forName使用反射去尝试,能否通过系统猜测的类名,去得到对应的类对象,来推测是什么类型。
2、initializers的初始化

通过set方法完成SpringApplication成员变量initializers,即一些初始化的相关操作。它是一个List<ApplicationContextInitializer>,这些,初始化的应用上下文实例,创建是通过反射创建的。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return this.getSpringFactoriesInstances(type, new Class[0]);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}    
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList(names.size());
    Iterator var7 = names.iterator();
    while(var7.hasNext()) {
        String name = (String)var7.next();
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        } catch (Throwable var12) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
        }
    }
    return instances;
}

在getSpringFactoriesInstances方法里面根据当前线程获取到其类的加载器,然后调用下面的方法
SpringFactoriesLoader.loadFactoryNames(type, classLoader)

这个方法会从META-INF/spring.factories配置文件中根"type"(就是ApplicationContextInitializer的全路径)作为key获取所有的值,通过DEBUG模式我们可以看到返回的names,见下图:


而在createSpringFactoriesInstances()方法中,通过反射的方式创建出各个ApplicationContextInitializer所有实现类的实例,然后添加到List中返回。setInitializers()方法最终完成SpringApplication类中initializers变量的注入。
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList();
    this.initializers.addAll(initializers);
}

我个人理解这一步是把,为上下文的初始准备的类给安装到这个list参数里面,可能是后面run中的上下文创建有关(以后再看看)。
3、成员变量listeners的初始化

这一步其实和上面的setInitializers的创建过程是一样的,只是把上下文初始化实例换成了需要的监听器实例

 

4、获取当前应用的启动类的类对象,顺便看下方法吧,比较简单:
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;
}

先获取到所有的栈元素和正在执行的方法,如果是"main"方法则返回这个类的类对象(这个就不做深入了解了)

 

二、SpringApplication实例run方法执行

这个方法应该可以代表整个应用的启动过程了,方法里面内容很多,只说一些我认为比较重要的。

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        this.refreshContext(context);
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

上面方法执行完返回的是一个ConfigurableApplicationContext对象,也就是一个可配置应用的上下文对象,方法返回后我们的程序也就启动完成了。(其实来我这里主要就是想看看它对各种bean类的加载顺序)
1、应用启动监听器执行监听

我们先看这行代码:
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();

SpringApplicationRunListeners包含了一个SpringApplicationRunListener实现类的List集合。

getRunListeners()方法中,会先通过new的方法创建SpringApplicationRunListeners实例,它需要两个参数,一个是Log,一个是SpringApplicationRunListener实现类对象的List。

而SpringApplicationRunListener实现类的创建和上面的initializers和listeners初始化过程几乎完全相同,通过反射创建出其实现类的实例.

但是通过DEBUG发现SpringApplicationRunListener只有一个实现类就是EventPubulishingRunListener,

它实现了SpringApplicationRunListener和Ordered两个接口。因此执行listeners.starting()方法实际上调用的是EventPubulishingRunListener实例的starting()方法。另外在EventPubulishingRunListener实例化的过程中会注入前面的SpringApplication实例,和SpringApplication的成员变量listeners,其中listeners会被注入到EventPubulishingRunListener的成员变量initialMulticaster中。EventPubulishingRunListener的starting()方法:
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

根据上面代码可见创建了一个ApplicationStartingEvent事件,然后由initialMulticaster将这个事件广播给所有监听ApplicationStartingEvent事件类型的监听器,最后监听器执行监听。starting的代码量感觉量有点多,这里就不继续深入了
2、准备环境监听器执行(配置文件加载)

接下来看这行代码:
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared((ConfigurableEnvironment)environment);
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
    }

    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();
        }
    }
}

根据名称我们应该就能猜测到这个应该是准备应用启动环境的。注意里面的listeners,可以直接认为就是一个EventPubulishingRunListener实例。prepareEnvironment()中会先根据应用的类型创建一个ConfigurableEnvironment实例,因为我们的应用类型是"SERVLET",因此创建出的实例实际上是StandardServletEnvironment。


然后执行configureEnvironment()方法,感觉这个是和配置相关的方法,看下面代码:

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);
}

根据名称应该是和应用配置的一些操作,它先判断是否添加添加转换服务(这个就看看现在也不知道它干啥用)

方法configurePropertySources()主要是查看有没有指定新的配置源(通过命令行指定),因为我们并没有通过命令行指定,这里就不往下继续看代码了。
第二个方法是configureProfiles(),看名字应该是和配置文件相关的,那么我们的application.properties是不是应该在这里面加载的呢??我们接着往下看configureProfiles()方法:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    environment.getActiveProfiles();
    Set<String> profiles = new LinkedHashSet(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

这里通过AbstractEnvironment的方法获取所有的"ActiveProfiles",然后添加的应用环境的"activeProfiles"变量中去。而AbstractEnvironment获取"ActiveProfiles"就是获取配置文件的"spring.profiles.active"属性来获取相应的文件名称的,但是这里并没有能获取到任何的"ActiveProfiles",按道理应该是可以得到对应得启动配置文件的名称,通过debug发现要在执行下面这个监听器的环境准备后才能在environment中找到对应的配置文件。


那只能继续往下执行下面的方法:
listeners.environmentPrepared((ConfigurableEnvironment)environment);

前文说到了,这个"listeners"其实就是EventPubulishingRunListener,是一个发布事件的监听器。这里它会创建一个ApplicationEnvironmentPreparedEvent实例,然后是通过SimpleApplicationEventMulticaster这一事件广播器拿到对应的监听器,并执行。这里最终拿到的监听器是ConfigFileApplicationListener,它会根据配置文扩展名以及默认配置文件名"application",在"classpath:/,classpath:/config/,file:./,file:./config/"下面寻找配置文件,这里面的方法感觉比较复杂,很多次的遍历(当时看的很晕),有兴趣可以去仔细看一下这部分代码。
到这里就完成我们应用配置文件的加载。
这个中间其实还是有个banner输出我就不说了,直接看看我们要了解的重点这个上下文的构建

3、创建应用上下文


context = this.createApplicationContext();

这个方法会根据我们应用的类型,完成应用ConfigurableApplicationContext的创建,上面也有提到,我们的应用是一个"servlet",这个方法里也是根据反射获取到对应"context"的类对象,然后进行实例化。最终创建出的是AnnotationConfigServletWebServerApplicationContext实例,根据名称就可以知道这是一个注解可配置的web应用的上下文对象。


4、创建各种bean对象
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);

这三个方法放到一起说,根据方法名称可以看首先将之前创建出来的content容器进行准备处理(可以理解为初始),

然后刷新容器。自己跟踪了一下代码,但是感觉自己能力有限,有些内容并不太清楚,所以只能简单的介绍一下。
首先会将在"SpringApplication"中的initializers的几个对象作为注入到"context"容器中,这个过程中也会向"context"容器中注入beanFactoryProcessor,此外还会广播ApplicationPreparedEvent事件给监听器,启动相关的监听器,此外我们自定义的bean,比如controller、service等组件也会在refreshContext()方法执行后注入到"context"容器中(这个我通过debug看到bean名称猜测这个注册的顺序应该和bean的名称有关但具体是什么关系实在难以读下去),自己能力有限,跟踪代码把自己都跟丢了....有些方法又是云里雾里,看来还是自己对spring不熟悉的原因,等自己有时间再一点点的看吧。(不过如果有需要指点bean的执行顺序可以同过order,@AutoConfigureAfter@DependsOn)来解决,这里给出一篇博客关于这3个的用法https://www.cnblogs.com/yihuihui/p/11761105.html
5、应用启动完成
listeners.started(context);

这个方法最终执行的也是EventPublishingRunListener的started(),即有"context"容器发布应用启动的事件,即:ApplicationStartedEvent,这里整个应用就已经启动成功了。
6、runner执行
this.callRunners(context, applicationArguments);

之所以说这个方法,是因为上次做spring boot项目的时候用到过,我觉得有必要说一下。spring boot提供了两个接口CommandLineRunner和ApplicationRunner,如果你想在项目启动后做一些操作的话,实现这两个接口重写run方法就可以了,我感觉也是非常的方便,比如添加一些数据到缓存中。这两个接口的实现类也会做为组件在refreshContext方法之后注入到"context"容器中。
以上就是spring boot的启动,自己也是第一次专门的看框架源码,不知从何处着手,所以就只能大概的分析下启动的过程,而且有很多地方自己看的还不够深入,还有各个监听器的作用也需要自己去了解,等自己有时间再来学习一下。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值