@EnableAutoConfiguration、@ComponentScan和@Configuration
@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?
注:意见或者建议请评论留言。转载请标明作者与来源。