该系列文章是笔者在学习 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 的自动配置功能进行展述
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 提供的,标注后表示当前注解定义在某个类上时,其子类会继承该注解,我们一起来看看其他三个注解
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 则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。
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 会选在以该注解标注的类所在的包作为基础路径,然后扫描包下面的这些类
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 管理
DeferredImportSelector
接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持 @Order
排序该 Class 对象实现了 ImportBeanDefinitionRegistrar
接口,会调用它的 registerBeanDefinitions(..)
方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身)
该 Class 对象是一个 @Configuration
配置类,会将这个类作为一个 Bean 被 Spring IoC 管理
对于 @Import
注解不熟悉的小伙伴可查看我前面的 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》 这篇文章
这里的 @EnableAutoConfiguration
自动配置模块驱动注解,通过 @Import
导入 AutoConfigurationImportSelector 这个类(实现了 DeferredImportSelector
接口)来驱动 Spring Boot 的自动配置模块,下面会进行分析
我们注意到 @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 包供他人使用