VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解

该系列文章是笔者在学习 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 进行源码分析的文章:

  • 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》
  • 《死磕Spring之IoC篇 - BeanDefinition 的解析过程(面向注解)》

该注解通常需要和 @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 注解的值有三种情况:

  1. 该 Class 对象实现了 ImportSelector 接口,调用它的 selectImports(..) 方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理

    • 该 Class 对象实现了 DeferredImportSelector 接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持 @Order 排序
  2. 该 Class 对象实现了 ImportBeanDefinitionRegistrar 接口,会调用它的 registerBeanDefinitions(..) 方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身)

  3. 该 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 包供他人使用


相关教程