该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读
Spring Boot 版本:2.2.x
最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章
如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~
该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》
概述
现如今,Spring Boot
在许多中大型企业中被普及,想必大家对于 @SpringBootApplication
并不陌生,这个注解通常标注在我们应用的启动类上面,标记是一个 Spring Boot 应用,同时开启自动配置的功能,那么你是否有深入了解过该注解呢?没有的话,或许这篇文章可以让你对它有一个新的认识。
提示:
@EnableAutoConfiguration
是开启自动配置功能的模块驱动注解,是 Spring Boot 的核心注解整篇文章主要是对这个注解,也就是 Spring Boot 的自动配置功能进行展述
@SpringBootApplication
org.springframework.boot.autoconfigure.SpringBootApplication
注解在 Spring Boot 的 spring-boot-autoconfigre
子模块下,当我们引入 spring-boot-starter
模块后会自动引入该子模块
该注解是一个组合注解,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 表明该注解定义在某个类上时,其子类会继承该注解
@SpringBootConfiguration // 继承 `@Configuration` 注解
@EnableAutoConfiguration // 开启自动配置功能
// 扫描指定路径下的 Bean
@ComponentScan( excludeFilters = {
// 默认没有 TypeExcludeFilter
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
// 排除掉自动配置类
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* 需要自动配置的 Class 类
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* 需要自动配置的类名称
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* 需要扫描的路径
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* 需要扫描的 Class 类
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
/**
* 被标记的 Bean 是否进行 CGLIB 提升
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@SpringBootApplication
注解就是一个组合注解,里面的每个配置都是元注解中对应的属性,上面已做描述
该注解上面的 @Inherited
元注解是 Java 提供的,标注后表示当前注解定义在某个类上时,其子类会继承该注解,我们一起来看看其他三个注解
@SpringBootConfiguration
org.springframework.boot.SpringBootConfiguration
注解,Spring Boot 自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
/**
* 被标记的 Bean 是否进行 CGLIB 提升
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
该注解很简单,上面标注了 @Configuration
元注解,所以作用相同,同样是将一个类标注为配置类,能够作为一个 Bean 被 Spring IoC 容器管理
至于为什么不直接使用 @Configuration
注解呢,我想这应该是 领域驱动设计 中的一种思想,可以使得 Spring Boot 更加灵活,总有它的用武之地
领域驱动设计:Domain-Driven Design,简称 DDD。过去系统分析和系统设计都是分离的,这样割裂的结果导致需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。DDD 则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。
@ComponentScan
org.springframework.context.annotation.ComponentScan
注解,Spring 注解,扫描指定路径下的标有 @Component
注解的类,解析成 Bean 被 Spring IoC 容器管理
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* 指定的扫描的路径
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 指定的扫描的路径
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 指定的扫描的 Class 对象
*/
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
boolean useDefaultFilters() default true;
/**
* 扫描时的包含过滤器
*/
Filter[] includeFilters() default {};
/**
* 扫描时的排除过滤器
*/
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
}
想深入了解该注解的小伙伴可以查看我前面对 Spring IoC 进行源码分析的文章:
该注解通常需要和 @Configuration
注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @ComponentScan
注解后则处理该注解,通过 ClassPathBeanDefinitionScanner 扫描器去扫描指定路径下标注了 @Component
注解的类,将他们解析成 BeanDefinition(Bean 的前身),后续则会生成对应的 Bean 被 Spring IoC 容器管理
当然,如果该注解没有通过 basePackages
指定路径,Spring 会选在以该注解标注的类所在的包作为基础路径,然后扫描包下面的这些类
@EnableAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration
注解,Spring Boot 自定义注解,用于驱动 Spring Boot 自动配置模块
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 注册一个 Bean 保存当前注解标注的类所在包路径
@Import(AutoConfigurationImportSelector.class) // Spring Boot 自动配置的实现
public @interface EnableAutoConfiguration {
/**
* 可通过这个配置关闭 Spring Boot 的自动配置功能
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 指定需要排除的自动配置类的 Class 对象
*/
Class<?>[] exclude() default {};
/**
* 指定需要排除的自动配置类的名称
*/
String[] excludeName() default {};
}
对于 Spring 中的模块驱动注解的实现都是通过 @Import
注解来实现的
模块驱动注解通常需要结合 @Configuration
注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @Import
注解后则进行处理,对于 @Import
注解的值有三种情况:
-
该 Class 对象实现了
ImportSelector
接口,调用它的selectImports(..)
方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理- 该 Class 对象实现了
DeferredImportSelector
接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持@Order
排序
- 该 Class 对象实现了
-
该 Class 对象实现了
ImportBeanDefinitionRegistrar
接口,会调用它的registerBeanDefinitions(..)
方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身) -
该 Class 对象是一个
@Configuration
配置类,会将这个类作为一个 Bean 被 Spring IoC 管理
对于 @Import
注解不熟悉的小伙伴可查看我前面的 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》 这篇文章
这里的 @EnableAutoConfiguration
自动配置模块驱动注解,通过 @Import
导入 AutoConfigurationImportSelector 这个类(实现了 DeferredImportSelector
接口)来驱动 Spring Boot 的自动配置模块,下面会进行分析
@AutoConfigurationPackage
我们注意到 @EnableAutoConfiguration
注解上面还有一个 @AutoConfigurationPackage
元注解,它的作用就是注册一个 Bean,保存了当前注解标注的类所在包路径
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/**
* 将当前注解所标注的类所在包名封装成一个 {@link org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages} 进行注册
* 例如 JPA 模块的会使用到这个对象(JPA entity scanner)
*/
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage { }
同样这里使用了 @Import
注解来实现的,对应的是一个 AutoConfigurationPackages.Registrar
内部类,如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册一个 BasePackages 类的 BeanDefinition,角色为内部角色,名称为 `org.springframework.boot.autoconfigure.AutoConfigurationPackages`
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
// 将注解元信息封装成 PackageImport 对象,对注解所在的包名进行封装
return Collections.singleton(new PackageImport(metadata));
}
}
比较简单,这里直接跳过了
自动配置
在开始之前,我们先来了解一下 Spring Boot 的自动配置,就是通过引入某个功能的相关 jar
包依赖后,Spring Boot 能够自动配置应用程序,让我们很方便的使用该功能
-
例如当你引入
spring-boot-starter-aop
后,会自动引入 AOP 相关的jar
包依赖,那么在spring-boot-autoconfigure
中有一个AopAutoConfiguration
自动配置类会自动驱动整个 AOP 模块 -
例如当你引入
spring-boot-starter-web
后,会自动引入 Spring MVC、Tomcat 相关的jar
包依赖,那么在spring-boot-autoconfigure
中会有相应的自动配置类会自动配置 Spring MVC
当然,还有许多自动配置类,结合这 Spring Boot 的 Starter 模块,让许多功能或者第三方 jar
包能够很简便的和 Spring Boot 整合在一起使用
现在很多开源框架都提供了对应的 Spring Boot Starter 模块,能够更好的整合 Spring Boot,当你熟悉自动配置功能后,你也可以很轻松的写一个 Starter 包供他人使用