注解分析

@SpringBootApplication 是 Sprnig Boot 项目的核心注解,目的是开启自动配置。它一般作用于 Spring Boot 应用的启动类上,主要由 @ComponentScan,@SpringBootConfiguration 和 @EnableAutoConfiguration 三个注解合成而来。以下展示了 @SpringBootApplication 注解的层次关系,接下来将详细讲解每个注解的作用。

1
2
3
4
5
6
7
8
@SpringBootApplication
├── @ComponentScan
├── @SpringBootConfiguration
│ └── @Configuration
└── @EnableAutoConfiguration
├── @Import(AutoConfigurationImportSelector.class)
└── @AutoConfigurationPackage
└── @Import(AutoConfigurationPackages.Registrar.class)

@ComponentScan

1
2
3
4
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})

@ComponentScan 用于实现自动扫描组件并将其添加到 IoC 容器中。

可以通过 basePackages 属性来指定自动扫描的包路径,不指定时默认在 @ComponentScan 所在类的包下进行扫描,因此 Spring Boot 启动类所属包下的所有类都能被扫描。

@SpringBootApplication 中的 @ComponentScan 还利用 excludeFilters 属性排除了对 TypeExcludeFilter 和 AutoConfigurationExcludeFilter 两个类的扫描。

@SpringBootConfiguration

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documenteds
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}

@SpringBootConfiguration 本质上就是 @Configuration,它可将当前类标注为配置类,并会将当前类内声明的带有 @Bean 注解的方法注册到容器中,其实例名为方法名,类型为方法返回类型。

@EnableAutoConfigurations

@EnableAutoConfigurations 是实现自动配置的关键注解,主要由 @AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class) 两个注解合成,以下将分别讲解两个注解的作用。

@AutoConfigurationPackage

该注解包含 @Import(AutoConfigurationPackages.Registrar.class),其功能是将 Registrar 类注册到容器中,该类通过调用 register 方法可将包路径下所有带有 @Bean 注解的方法注册到组件中。

简单来讲,就是将用户自己写的 @Bean 注册到容器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
}

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}

@Import(AutoConfigurationImportSelector.class)

该注解是真正实现自动配置的注解,它将 AutoConfigurationImportSelector 类注册到容器中,目的是批量向容器中注册组件。

AutoConfigurationImportSelector 实现了 ImportSelector 接口,其中只需实现 selectImports 方法,并以数组的形式返回要导入的类名,即可实现批量注册组件。

AutoConfigurationImportSelector 类中通过调用 getAutoConfigurationEntry 方法来获取 Spring Boot 需要自动配置的类名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

通过 Debug 模式可以看到,在 Spring Boot 应用启动时会自动从 META-INF/spring.factories 中获取需要自动注册的配置类,当前项目共 131 个。这 131 个配置类存在于 org.springframework.boot.autoconfigure 包下,并都以 XXXAutoConfiguration 命名。

configuration

自动配置条件

在 Debug 模式下,可以获取最终自动配置的所有类,发现从全部的 131 个减少到了 24 个。

filter

这是因为 Spring Boot 将相应功能模块未开启的配置类筛选掉了,返回了真正需要自动配置的类名。

例如,在 WebMvcAutoConfiguration 类上存在 @ConditionalOnXXX 注解,该类注解作为配置类生效的条件。依然以 WebMvcAutoConfiguration 为例,只有在 Web 类型为 SERVLET(而非 REACTIVE);Servlet、DispatcherServlet、WebMvcConfigurer 类都存在;容器中没有 WebMvcConfigurationSupport 类型的 Bean 时,该配置类才会生效。如果没有导入 Spring Web 的依赖,那么 DispatcherServlet 类就不存在,配置类也就不起作用,自动配置时就会把 WebMvcAutoConfiguration 筛选掉。

1
2
3
4
5
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {
}

ConfigurationProperties

在对 Spring Boot 应用进行配置时,只需在 application.properties 或 application.yml 中填写相应的配置项,即可实现对配置类的修改,其背后的逻辑是利用 ConfigurationProperties 将配置类与配置文件前缀进行绑定,以下用 DataSource 的配置举例说明。

在配置数据源时通常需要有以下配置

1
2
3
4
5
6
spring:
datasource:
url: jdbc:mysql://101.37.23.44:3306/demo
username: demo
password: demo
driver-class-name: com.mysql.cj.jdbc.Driver

而在 Spring 源码中有与配置项对应的类名为 DataSourceProperties,该类通过 @ConfigurationProperties(prefix = “spring.datasource”) 注解将其绑定到配置文件中 spring.datasource 的配置项上。

1
2
3
4
5
6
7
8
9
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

private String driverClassName;
private String url;
private String username;
private String password;
// ………
}

在 DataSourceAutoConfiguration 自动配置类中又通过注解 @EnableConfigurationProperties(DataSourceProperties.class) 开启上述类与自动配置类的绑定。

1
2
3
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
}

这样一来,在之后的自动配置阶段 Spring Boot 自动注册到容器中的 DataSourceAutoConfiguration 就会带上配置文件中的信息了。