spring boot启动过程

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,能够自动引入这些自动化配置的类,这些类会自动的初始化很多默认值。就此完成了自动化配置工作。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值