spring boot启动过程

来源:互联网 发布:小型宝塔数控机床编程 编辑:程序博客网 时间:2024/05/17 11:05

spring已经成为实时上的J2EE标准,spring boot并没有提供太多新的特性,而是发现了大部分的模板配置,没必要重复的配置,而且现在脚本语言大行其道,并且微服务的诞生让更多项目的构建和部署,spring这些大量的配置文件带来很多不必要的工作量。

spring boot顾名思义能够自动化的启动一个应用。以spring-boot-starter-web为例,它其实就是引入了一个组合pom.xml。它引入的pom如下:

<!-- spring web 相关jar-->    <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter</artifactId>        </dependency>        <!-- 内置 tomcat 支持  -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-tomcat</artifactId>        </dependency>        <!-- 默认orm 采用hibernate -->        <dependency>            <groupId>org.hibernate</groupId>            <artifactId>hibernate-validator</artifactId>        </dependency>        <dependency>            <groupId>com.fasterxml.jackson.core</groupId>            <artifactId>jackson-databind</artifactId>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-web</artifactId>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-webmvc</artifactId>        </dependency>

spring-boot-starter能够引入spring核心的包,包括core, webmvc, aop, context,orm 等等。默认采用了hibernate去完成JPA相关工作。整个自动化过程主要再spring-boot-*包下面。

我们看下spring-boot-starter主要引入了:

<!-- spring boot 核心支持 --><dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot</artifactId>        </dependency>        <!-- spring boot默认配置支持 -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-autoconfigure</artifactId>        </dependency>        <!-- 默认日志支持 -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-logging</artifactId>        </dependency>        <dependency>            <groupId>org.springframework</groupId>            <artifactId>spring-core</artifactId>            <exclusions>                <exclusion>                    <groupId>commons-logging</groupId>                    <artifactId>commons-logging</artifactId>                </exclusion>            </exclusions>        </dependency>        <!-- yaml格式支持 -->        <dependency>            <groupId>org.yaml</groupId>            <artifactId>snakeyaml</artifactId>            <scope>runtime</scope>        </dependency>

整个自动化启动需要这几个组件。由于篇幅有限,我们通过应用的启动过程来跟踪包的作用。

这里写图片描述
一般我们应用的入口是SpringApplication,这个在根包下面。
我们启动一个应用如下:

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

它的构造函数:

/**     * Create a new {@link SpringApplication} instance. The application context will load     * beans from the specified sources (see {@link SpringApplication class-level}     * documentation for details. The instance can be customized before calling     * {@link #run(String...)}.     * @param sources the bean sources     * @see #run(Object, String[])     * @see #SpringApplication(ResourceLoader, Object...)     */    public SpringApplication(Object... sources) {        initialize(sources);    }

构造一个spring应用实例,从指定的源加载。接着调用initialize()初始化整个应用。源可以是一个或者多个。初始化:

private void initialize(Object[] sources) {        if (sources != null && sources.length > 0) {            this.sources.addAll(Arrays.asList(sources));        }        this.webEnvironment = deduceWebEnvironment();        setInitializers((Collection) getSpringFactoriesInstances(                ApplicationContextInitializer.class));            // 这里获取所有的ApplicationListener(框架自带或则用户自定义)        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));        this.mainApplicationClass = deduceMainApplicationClass();    }

这配置初始化的监听器,能够让用户可以跟踪整个应用的加载过程。准备好了就可以调用应用的run(args)函数。

/**     * Run the Spring application, creating and refreshing a new     * {@link ApplicationContext}.     * @param args the application arguments (usually passed from a Java main method)     * @return a running {@link ApplicationContext}     */    public ConfigurableApplicationContext run(String... args) {        // 计时器,能够跟踪应用的运行时间(主要开发环境用)        StopWatch stopWatch = new StopWatch();        stopWatch.start();        ConfigurableApplicationContext context = null;        FailureAnalyzers analyzers = null;        configureHeadlessProperty();        // 获取所有监听器,能够在应用的启动阶段收到通知(类似观察者模式)        SpringApplicationRunListeners listeners = getRunListeners(args);        listeners.starting();        try {            ApplicationArguments applicationArguments = new DefaultApplicationArguments(                    args);            ConfigurableEnvironment environment = prepareEnvironment(listeners,                    applicationArguments);            // 这里是一个彩蛋,可以在resources文件夹自定义banner.txt来覆盖默认启动的spring LOGO...            Banner printedBanner = printBanner(environment);            // 反射创建createApplicationContext容器。spring容器核心,注意这里这个ApplicationContext还是一个不能用的容器,等待后面的启动工作            context = createApplicationContext();            analyzers = new FailureAnalyzers(context);            // 做容器的初始化工作            prepareContext(context, environment, listeners, applicationArguments,                    printedBanner);            // 这里是spring容器启动            refreshContext(context);            afterRefresh(context, applicationArguments);            listeners.finished(context, null);            stopWatch.stop();            if (this.logStartupInfo) {                new StartupInfoLogger(this.mainApplicationClass)                        .logStarted(getApplicationLog(), stopWatch);            }            return context;        }        catch (Throwable ex) {            handleRunFailure(context, listeners, analyzers, ex);            throw new IllegalStateException(ex);        }    }

上面有两个过程是比较重要的,一个是prepareContext(context, environment, listeners, applicationArguments,printedBanner);一个是refreshContext(context);

对spring加载过程熟悉的读者肯定很熟悉refresh()函数。这个函数定义了整个容器加载的流程,其中包含一些模板方法,但是整个流程的骨架已经清晰可见。我们还是先讲讲prepareContext

private void prepareContext(ConfigurableApplicationContext context,            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,            ApplicationArguments applicationArguments, Banner printedBanner) {        context.setEnvironment(environment);        // 这里能够干预容器的加载,这是一个protected函数,意思就是可以继承并重写这个函数,如果有必要,你可以在这里做任何容器加载前的事情        postProcessApplicationContext(context);        // 调用定义的初始化类,这个在前面的initialize设置的        applyInitializers(context);        // 通知监听器        listeners.contextPrepared(context);        if (this.logStartupInfo) {            logStartupInfo(context.getParent() == null);            logStartupProfileInfo(context);        }context.getBeanFactory().registerSingleton("springApplicationArguments",                applicationArguments);        if (printedBanner != null) {            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);        }        // Load the sources        Set<Object> sources = getSources();        Assert.notEmpty(sources, "Sources must not be empty");        // 加载bean读取组件,让应用能够读取xml javaConfig等不同形式定义的bean        load(context, sources.toArray(new Object[sources.size()]));        // 加载完毕通知监听器        listeners.contextLoaded(context);    }

接下来开始加载bean并完成容器的启动工作:refreshContext(context); 可以参考博主另一文章:容器加载

好了,然后整个过程结束,等等,说好的spring boot呢,请不要急。我们一般在source,也就是启动应用的类前面加一个@SpringBootApplication,打开它的源码:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = {        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication { // ...}

它是一个组合注解,还包含了EnableAutoConfiguration。它的字面意思就是自动配置。我很想把它的JavaDoc放出来:

/**
* Enable auto-configuration of the Spring Application Context, attempting to guess and
* configure beans that you are likely to need. Auto-configuration classes are usually
* applied based on your classpath and what beans you have defined. For example, If you
* have {@code tomcat-embedded.jar} on your classpath you are likely to want a
* {@link TomcatEmbeddedServletContainerFactory} (unless you have defined your own
* {@link EmbeddedServletContainerFactory} bean).
*


* Auto-configuration tries to be as intelligent as possible and will back-away as you
* define more of your own configuration. You can always manually {@link #exclude()} any
* configuration that you never want to apply (use {@link #excludeName()} if you don’t
* have access to them). You can also exclude them via the
* {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied
* after user-defined beans have been registered.
*


* The package of the class that is annotated with {@code @EnableAutoConfiguration} has
* specific significance and is often used as a ‘default’. For example, it will be used
* when scanning for {@code @Entity} classes. It is generally recommended that you place
* {@code @EnableAutoConfiguration} in a root package so that all sub-packages and classes
* can be searched.
*


* Auto-configuration classes are regular Spring {@link Configuration} beans. They are
* located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
* Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
* often using {@link ConditionalOnClass @ConditionalOnClass} and
* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
*
* @author Phillip Webb
* @author Stephane Nicoll
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnClass
* @see AutoConfigureAfter
*/
开启自动配置spring应用,能够尝试去“猜测“你可能需要的配置,自动配置的类会作用在你的类路径基础上。举个例子,如果你有tomcat-embedded.jar,在你的类路径里,你可能会需要TomcatEmbeddedServletContainerFactory和EmbeddedServletContainerFactory这两个bean。
自动配置会尽可能智能的帮助你完成默认配置,你也可能会不希望使用很多默认配置,你可以通过excelude去移除自动配置支持。
注解本身不起任何作用,需要有对应的“注解处理器“。那我们就来找处理@EnableAutoConfiguration的处理器。自动化配置总是在bean定义被注册之后才会应用。在你标注了@EnableAutoConfiguration这个注解的类所在的包(包含子包)能够尽量的使用默认值。例如你扫描包的时候会以这个注解所在的包为根包开始扫描。自动配置类通常也是个spring bean。可以使用@Conditional来控制装配。

注解本身没有什么作用,只是个元数据而已,要配上注解处理器才能真正的起作用。AutoConfigurationImportSelector就是这个处理器.它的selectImports重写了ImportSelector,能够自动引入这些自动化配置的类,这些类会自动的初始化很多默认值。就此完成了自动化配置工作。