工作生活

[深入学习]Spring Boot中的SpringApplica

2019-07-15  本文已影响0人  lconcise

在Spring Boot的入口类中,我们通常是通过调用SpringApplication的run方法来启动Spring Boot项目。这节我们来深入学习下SpringApplication的一些细节。

  1. 自定义SpringApplication
    1.1 通过SpringApplication API 调整
    1.2 通过SpringApplicationBuilder API调整
  2. SpringApplication准备阶段
    2.1 配置源
    2.2 推断应用类型
    2.3 加载应用上下文初始器
    2.4 加载应用事件监听器
    2.5 推断入口类
  3. SpringApplication运行阶段
    3.1 开启事件监听
    3.2 开启运行监听器
    3.3 创建Environment
    3.4 是否打印Banner
    3.5 创建Context
    3.6 装配Context
    3.7 Refresh Context
    3.8 广播应用已开启
    3.9 执行Runner
    3.10 广播应用运行中

自定义SpringApplication

默认的我们都是直接通过SpringApplication的run方法来直接启动Spring Boot,其实我们可以通过一些API来调整某些行为。

通过SpringApplication API调整

创建一个SpringBoot项目,添加web 依赖。
然后将入口类代码修改为:

        SpringApplication application = new SpringApplication(Application.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.setWebApplicationType(WebApplicationType.NONE);
        application.setAdditionalProfiles("dev");
        application.run(args);

通过调用SpringApplication的方法,我们关闭了Banner的打印,设置应用环境为非WEB应用,profiles指定为dev。除此之外,SpringApplication还包含了许多别的方法,具体可以查看源码或者官方文档:


image.png

通过SpringApplicationBuilder API调整

SpringApplicationBuilder提供了Fluent API,可以实现链式调用,下面的代码和上面的效果一致,但在编写上较为方便:

        new SpringApplicationBuilder(Application.class)
                .bannerMode(Banner.Mode.OFF)
                .web(WebApplicationType.NONE)
                .profiles("dev")
                .run(args);

SpringApplication准备阶段

SpringApplication的生命周期阶段大致可以分为准备阶段和运行阶段。
我们通过源码来查看SpringApplication的有参构造器:

    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.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的准备阶段分为以下几个步骤:

配置源

构造器中this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));这行代码用于加载我们配置的Spring Boot Bean源。通常我们使用SpringApplication或者SpringApplicationBuilder的构造器来直接指定源。

所谓的Spring Boot Bean源指的是某个被@SpringBootApplication注解标注的类,比如入口类:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(Application.class);
        application.run(args);
}

我们也可以将上面的代码改为下面这种方式:

public class Application {

    public static void main(String[] args) {
       SpringApplication application = new SpringApplication(ApplicationResource.class);
        application.run(args);
}
    @SpringBootApplication
    public static class ApplicationResource {

    }
}

这样也是可行的。查看SpringApplication的单个参数构造器:

    public SpringApplication(Class... primarySources) {
        this((ResourceLoader)null, primarySources);
    }

说明我们除了配置单个源外,还可以配置多个源。

推断应用类型

构造器中这行this.webApplicationType = WebApplicationType.deduceFromClasspath();代码用于推断当前Spring Boot应用类型

Spring Boot 2.0后,应用可以分为下面三种类型:

  1. WebApplicationType.NONE:非WEB类型;
  2. WebApplicationType.REACTIVE:Web Reactive类型;
  3. WebApplicationType.SERVLET:Web Servlet类型。
    WebApplicationType.deduceFromClasspath()或根据当前应用ClassPath中是否存在相关的实现类来判断应用类型到底是哪个,deduceFromClasspath方法的源码如下所示:
    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;
        }
    }

我们也可以直接通过SpringApplication的setWebApplicationType方法或者SpringApplicationBuilder的web方法来指定当前应用的类型。

加载应用上下文初始器

接着下一行代码setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));用于加载应用上下文初始器ApplicationContextInitializer。

getSpringFactoriesInstances方法的源码如下所示:

    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 = this.getClassLoader();
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

上面代码利用Spring工厂加载机制,实例化ApplicationContextInitializer实现类,并进行排序。
所以我们可以通过实现ApplicationContextInitializer接口用于在Spring Boot应用初始化之前执行一些自定义操作。

@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("ConfigurableApplicationContext.id -" + configurableApplicationContext.getId());
    }
}

上面代码中实现了initialize方法,并且使用@Order注解指定优先级。其中Ordered.HIGHEST_PRECEDENCE等于Integer.MIN_VALUE,Ordered.LOWEST_PRECEDENCE等于Integer.MAX_VALUE。所以数值越小,优先级越高。

除了使用@Order注解来指定优先级外,我们也可以通过实现org.springframework.core.Ordered接口的getOrder方法来指定优先级。

public class AfterHelloApplicationContextInitializer implements ApplicationContextInitializer ,Ordered{
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("AfterConfigurableApplicationContext.id -" + configurableApplicationContext.getId());
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

上面通过getOrder方法来指定了优先级为最低优先级。

创建好后,我们还需在工厂配置文件里配置这两个实现类。在resources目录下新建META-INF目录,并创建spring.factories文件:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
top.lconcise.config.HelloApplicationContextInitializer,\
top.lconcise.config.AfterHelloApplicationContextInitializer

加载应用事件监听器

在加载完应用上下文初始器后,下一行的setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));代码加载了应用事件监听器。与加载事件上下文初始器类似,Spring Boot也是通过Spring的工厂方法来实例化ApplicationListener的实现类,并进行排序。
既然是事件监听,那么其可以监听什么事件呢?其监听的是ApplicationEvent接口的实现类,我们查看一下都有哪些事件实现了这个接口:

这里我们以ContextClosedEvent为例子来编写自定义的应用事件监听器,监听Spring上下文关闭事件。

@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> {
    @Override
    public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
        System.out.println("ContextClosedEvent: " + contextClosedEvent.getApplicationContext().getId());
    }
}
public class AfterContextClosedEventListener implements ApplicationListener<ContextClosedEvent>, Ordered {
    @Override
    public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
        System.out.println("AfterContextClosedEvent: " + contextClosedEvent.getApplicationContext().getId());
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}
# Application Listeners
org.springframework.context.ApplicationListener=\
top.lconcise.listener.ContextClosedEventListener,\
top.lconcise.listener.AfterContextClosedEventListener

推断入口类

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

SpringApplication运行阶段

SpringApplication的运行阶段对应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);
        }
    }

运行阶段大致可以分为下面这几个过程:

开启时间监听

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

上面代码用于开启Spring Boot应用启动时间监听,配合下面的stopWatch.stop();便可以计算出完整的启动时间。

开启运行监听器

        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

getRunListeners方法源码:

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

上面代码通过SpringFactoriesLoader检索META-INF/spring.factories找到声明的所有SpringApplicationRunListener的实现类并将其实例化,然后装配到List<SpringApplicationRunListener>运行监听器集合中。

listeners.started();用于遍历运行监听器集合中的所有SpringApplicationRunListener的实现类,并逐一调用它们的starting方法,广播Spring Boot应用要开始启动了。

在Spring Boot中SpringApplicationRunListener接口用于监听整个Spring Boot应用生命周期,其代码如下所示:

public interface SpringApplicationRunListener {
    void starting();

    void environmentPrepared(ConfigurableEnvironment environment);

    void contextPrepared(ConfigurableApplicationContext context);

    void contextLoaded(ConfigurableApplicationContext context);

    void started(ConfigurableApplicationContext context);

    void running(ConfigurableApplicationContext context);

    void failed(ConfigurableApplicationContext context, Throwable exception);
}

创建 Environment

run方法中的这行代码用于创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile):

onfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);

我们已经在准备阶段里推断出了应用类型,这里只要根据相应的应用类型来创建相应的应用环境即可,类型和环境对应关系如下:

在prepareEnvironment方法中会执行listeners.environmentPrepared(environment);,用于遍历调用所有SpringApplicationRunListener实现类的environmentPrepared()方法,广播Environment准备完毕。

是否打印Banner

run方法中的这行代码会根据我们的配置来决定是否打印Banner:

Banner printedBanner = this.printBanner(environment);

创建Context

run方法中的这行代码用于创建ApplicationContext

context = this.createApplicationContext();

不同的环境对应不同的ApplicationContext:

装配Context

run方法中的这行代码用于装配Context:

this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

方法prepareContext的源码如下所示:

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

        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        this.load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

prepareContext方法开头为ApplicationContext加载了environment,之后通过applyInitializers方法逐个执行ApplicationContextInitializer的initialize方法来进一步封装ApplicationContext,并调用所有的SpringApplicationRunListener实现类的contextPrepared方法,广播ApplicationContext已经准备完毕了。

之后初始化IOC容器,并调用SpringApplicationRunListener实现类的contextLoaded方法,广播ApplicationContext加载完成,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类。

Refresh Context

run方法中的这行代码用于初始化所有自动配置类,并调用ApplicationContext的refresh方法:

this.refreshContext(context);

广播应用已启动

run方法中的这行代码用于广播Spring Boot应用已启动:

listeners.started(context);

started方法会调用所有的SpringApplicationRunListener的finished方法,广播SpringBoot应用已经成功启动。

执行Runner

run方法中的这行代码callRunners(context, applicationArguments);遍历所有ApplicationRunner和CommandLineRunner的实现类,并执行其run方法。我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对Spring Boot的启动过程进行扩展。

@Component
public class HelloApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner :  Hello SpringBoot ");
    }
}

这里我们需要将HelloApplicationRunner使用@Component注解标注,让其注册到IOC容器中。

@Component
public class HelloCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner  Hello SpringBoot ");
    }
}

程序运行输出如下:

ApplicationRunner :  Hello SpringBoot 
CommandLineRunner :  Hello SpringBoot 

广播应用运行中

run方法中的这行代码listeners.running(context);用于调用SpringApplicationRunListener的running方法,广播Spring Boot应用正在运行中。

当run方法运行出现异常时,便会调用handleRunFailure方法来处理异常,该方法里会通过listeners.failed(context, exception);来调用SpringApplicationRunListener的failed方法,广播应用启动失败,并将异常扩散出去。

上面所有的广播事件都是使用Spring的应用事件广播器接口ApplicationEventMulticaster的实现类SimpleApplicationEventMulticaster来进行广播的。

源码地址:https://github.com/lbshold/springboot/tree/master/Spring-Boot-SpringApplication

参考文章

https://mrbird.cc/deepin-springboot-application.html

上一篇下一篇

猜你喜欢

热点阅读