@SpringBootApplication注解相当于@EnableAutoConfiguration、@ComponentScan和@Configuration这三个注解的组合。本文重点说明了自动配置的过程。

1.@EnableAutoConfiguration
这个注解的作用是启用Spring应用上下文的自动配置,尝试猜测并配置你可能需要的Bean。比如你依赖了tomcat-embedded.jar,那么它会推断你正在开发的是一个web应用,根据这个推断他会将servlet容器相关的bean注册到IOC容器,比如TomcatServletWebServerFactory。假如你引入了spring-boot-starter-web依赖那它会推断你要开发的是一个基于Spring MVC的Web应用,它会扫描你的整个上下文(包括classpath依赖的jar和你自定义的类),去满足这个应用的依赖。通常通过@SpringBootApplication来注解@EnableAutoConfiguration的类的包具有特定的意义,表示这个包是该应用的基础包,这个包下的所有子包都会被扫描。应该说,启用推断的应用它的推断是依赖它引入的Starter,所以spring-boot-starter-webflux被引入后会推断其应用是一个响应式web应用。
在spring-boot-autoconfigure包下定义好了各种应用的条件,在启用@EnableAutoConfiguration后,所有的/META-INF/spring.factories将被自动解析然后根据配置注册bean,如下,你可以看到默认会将所有自动配置都应用:

# Auto Configure 所有类型的应用
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
。。。。。

那么既然所有的应用都自动配置进去了,它如何确定到底需要加载哪些bean呢?答案就是之前说的定义好的condition:

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}

这里可以看到,只有在应用类型为Servlet容器的WebApplication时才会满足SpringMVC的自动配置(还有一堆其他条件都满足的情况下才会)。由此我们可以推断出自动配置的过程(以Web应用为例):
a.引入Starter会引入相应的jar,当Spring扫描上下文时发现有tomcat-embedded.jar,它推断你正在开发一个Servlet Tomcat Web应用。

//servlet web 自动配置
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, //tomcat
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
}
//Tomcat.class,UpgradeProtocol.class是tomcat-embed-core.jar里面的类
    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {

        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }

    }

你可以看到Type.SERVLET这个应用类型是在应用启动之前推断出来的。那么它到底是如何推断出来的呢?
首先Spring默认的应用类型就只有三种:普通java应用,基于Servlet的Web应用和基于响应的WEB应用。他们的应用上下文分别为:AnnotationConfigApplicationContext,AnnotationConfigServletWebServerApplicationContext,AnnotationConfigReactiveWebServerApplicationContext。所以推断的基本逻辑就出来了:

public enum WebApplicationType {
    /**普通java应用     */
    NONE,
    /**servlet web */
    SERVLET,
    /**reactive web*/
    REACTIVE;

    private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

    private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

    private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

    private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

/** 在应用未创建之前调用,推断应用类型*/
    static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }
/** 在应用未创建之后调用,推断应用类型*/
    static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
        if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
            return WebApplicationType.SERVLET;
        }
        if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
            return WebApplicationType.REACTIVE;
        }
        return WebApplicationType.NONE;
    }
    private static boolean isAssignable(String target, Class<?> type) {
        try {
            return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
        }
        catch (Throwable ex) {
            return false;
        }
    }
}

这里重点是应用上下文未创建之前的推断:
(a).在classpath下,DispatcherHandler存在,DispatcherServlet和ServletContainer不存在,则推断为响应式web应用。
(b).在(a)不满足的前提下,Servlet和ConfigurableWebApplicationContext任意一个不存在,推断为普通应用。
(c).在(a)和(b)都不满足的前提下,推断为Servlet web应用。
b.在推断出应用类型后@ConditionalOnWebApplication发挥作用,会将满足条件的bean注册到容器,比如:SpringMVC的相关bean。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {}

注意:自动配置的优劣十分依赖Condition,在我们自己写Starter时,仅需要将自动配置类配置到/META-INF/spring.factories,还需要未其设置好良好的自动配置的前置条件。在SpringMVC和SpringWebFlux共存的时候,默认为Servlet应用,你可以使用setWebApplicationType方法设置应用类型。

2.@ComponentScan
上文提到,@SpringBootApplication所在的包是组件扫描的基础包(其他组件通过自动配置注入),基础包这个功能就是通过@ComponentScan做到的,该注解如果没有定义特定的包,则将从声明此注解的类的包进行扫描。

3.@Configuration
你需要了解的是SpringApplication的run方法的所有参数都是配置。在@SpringBootApplication里面的是@SpringBootConfiguration,表示一个类提供了Spring Boot应用程序的@Configuration。可以用来替代Spring的标准@Configuration注解,以便自动找到配置,它只是一个标记,主要的为了方便测试,@SpringBootTest标记的测试类依赖这个注解找到配置。
原生的@Configuration表示一个类声明了一个或多个@Bean方法,能被Spring容器处理,以在运行时控制bean的生命周期。

扩展:Spring是如何处理@EnableAutoConfiguration注解的,以及该如何优雅得实现自己得starter?

注:意见或者建议请评论留言。转载请标明作者与来源。

标签: springboot

添加新评论