Spring Boot 最核心的功能就是“自动配置”,这一切都基于“约定优于配置”的原则。那么 Spring Boot 是如何约定,又是如何实现自动配置功能的呢?
下面带大家来通过源码学习 Spring Boot 的核心运作原理以及最核心的注解 @EnableAutoConfiguration
来进行讲解。
使用 Spring Boot 时我们只需引入对应的 Starters
,Spring Boot 启动时便自会动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是 Spring Boot 的自动配置功能。先从整体上看一下 Spring Boot 实现该运作机制涉及到的核心部分,如图 1。
图中描述了 Spring Boot 自动配置运作过程中涉及的几个核心功能及其相互之间的关系。
它们包括:@EnableAutoConfiguration、spring.factories、各组件对应的 AutoConfiguration类、 @Conditional 注解以及各种 Starters。
如果用一句话来描述整个过程就是:
Spring Boot 通过 @EnableAutoConfiguration 注解开启自动配置,加载 spring.factories 中注册的各种 AutoConfiguration 类,当某个 AutoConfiguration 类满足其注解 @Conditional 指定的生效条件(Starters 提供的依赖、配置或 Spring 容器中是否存在某个 Bean 等)时,那么实例化该 AutoConfiguration 类中定义的 Bean(组件等),并注入 Spring 容器,至此就完成了依赖框架的自动配置。
先从概念及功能上了解一下上图所属部分的作用及相互关系,后面章节会针对每个功能及组件进行源代码级别的讲解。
@EnableAutoConfiguration
: 该注解由组合注解 @SpringBootApplication引入,完成自动配置开启,扫描各个 jar 包下的spring.factories文件,并加载其中注册的 AutoConfiguration 类等。
spring.factories
: 配置文件,位于 jar 包的 META-INF 目录下,按照指定格式注册了自动配置的 AutoConfiguration 类,也可以包含其他类型待注册的类。该配置文件不仅存在于 Spring Boot 项目中,也可以存在于自定义的自动配置(或 Starter)项目中。
AutoConfiguration
: 自动配置类,代表了 Spring Boot 中一类以 XXXAutoConfiguration 命名的自动配置类。其中定义了第三方组件集成 Spring 所需初始化的 Bean 和条件。
@Conditional
: 条件注解及其衍生注解,使用在 AutoConfiguration 类上,当满足该条件注解时才会实例化 AutoConfiguration类 。
Starters
:第三方组件的依赖及配置,Spring Boot 已经预置的组件。Spring Boot 默认的 Starters项目往往是只包含了一个 pom 依赖的项目。如果自定义的 starter,该项目还需包含 spring.factories 文件、 AutoConfiguration 类和其他配置类。
以上在概念层面了解了 Spring Boot 自动配置的整体流程和基本运作原理,下面重点看一下@EnableAutoConfiguration 注解的功能。
@EnableAutoConfiguration 是开启自动配置的注解,在创建的 Spring Boot 项目中并不能直接看到此注解,它是由组合注解 @SpringBootApplication 引入。下面,我们首先了解一下入口类和 @SpringBootApplication 注解,然后再深入了解 @EnableAutoConfiguration 注解的构成与作用。
在Java中,main()方法是Java应用程序的入口方法,也就是说,程序在运行的时候,第一个执行的方法就是main()方法,这个方法和其他的方 法有很大的不同,比如方法的名字必须是main,方法必须是public static void 类型的,方法必须接收一个字符串数组的参数等等。
Spring Boot 项目创建完成会默认生成一个 XXApplication 的入口类。默认情况下,无论通过 IDEA 或通过官网创建基于 Maven 的 Spring Boot 项目,入口类的命名规则为:artifactId+Application。通过该类的 main 方法即可启动 Spring Boot 项目。
@SpringBootApplication
public class SpringLearnApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
关于 main 方法并无特别之处,就是一个标准的 Java 应用的 main 方法,这里是启动 Spring Boot 项目的入口。默认情况下,按照上述命名规则并包含 main 方法的类,我们以下称作“入口类”。
在入口类中,我们可以看到 Spring Boot 创建的代码中(除单元测试外)唯一的一个注解就是 @SpringBootApplication。它是 Spring Boot 项目的核心注解,用于开启自动配置,准确说是该注解内组合的 @EnableAutoConfiguration 开启了自动配置。
@SpringBootApplication 部分源代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
*
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
通过源代码可以看出,该注解提供了以下成员属性(注解中的成员变量以方法的形式体现):
exclude
:根据类(Class)排除指定的自动配置,该成员属性覆盖了 @SpringBootApplication 中组合的 @EnableAutoConfiguration 中定义的 exclude 成员属性。
excludeName
:根据类名排除指定的自动配置,同样覆盖了 @EnableAutoConfiguration 中的 excludeName 的成员属性。
scanBasePackages
:指定扫描的基础 package,用于激活 @Component 等注解类的初始化。
scanBasePackageClasses
:扫描指定的类,用于组件的初始化。
proxyBeanMethods
: 指定是否代理 @Bean 方法以强制执行 bean 的声明周期行为。此功能需要通过运行时生成 CGLIB 子类来实现方法拦截,该子类有一定的限制,比如配置类及其方法不允许声明为 final 等。默认值为 true,允许配置类中进行“inter-bean references”(bean 之间的引用)以及对该配置的 @Bean 方法的外部调用。如果 @Bean 方法都是自包含的,并且仅提供了容器使用的普通工程方法的功能,则可设置为 false,避免处理 CGLIB 子类。Spring Boot 2.2 新增该成员属性,后面章节涉及到的自动配置类中基本都会使用,一般情况下都配置为 false。
以上源代码会发现,大量使用了 @AliasFor 注解,该注解用于桥接到其他注解,该注解的属性中指定了所桥接的注解类。如果点进去查看,会发现 @SpringBootApplication 定义的属性,在其他注解中已经定义过了。之所以使用 @AliasFor 注解并重新在 @SpringBootApplication 中定义,更多的意义是为了减少用户使用多注解带来的麻烦。
@SpringBootApplication 注解中组合了 @SpringBootConfiguration 、@EnableAutoConfiguration 和 @ComponentScan。因此,在实践过程中也可以使用这三个注解来替代@SpringBootApplication。
在早期版本中并没有 @SpringBootConfiguration 注解,后新增了 @SpringBootConfiguration 并在其内组合了 @Configuration 。@EnableAutoConfiguration 注解组合了 @AutoConfigurationPackage。
忽略掉一些基础注解和元注解,@SpringBootApplication 注解的组合结构可以参考下图。
在上图中,@SpringBootApplication 除了组合元注解之外,核心包括:用于激活 Spring Boot 自动配置的 @EnableAutoConfiguration 、用于激活 @Component 扫描的@ComponentScan、用于激活配置类的 @Configuration。
其 @ComponentScan 注解和 @Configuration 注解在 Spring 的日常使用中经常用到,不再赘述,而关于 @EnableAutoConfiguration 正是我们下面要详细介绍的。
在未使用 Spring Boot 的情况下,Bean 的生命周期由 Spring 来管理,然而 Spring 无法自动配置@ Configuration注 解的类。而 Spring Boot 所做的核心之一就是根据约定可以自动化的管理该注解标注的类。用来实现该功能的组件之一便是 @EnableAutoConfiguration 注解。
@EnableAutoConfiguration 位于 spring-boot-autoconfigure 包内,当使用 @SpringBootApplication 注解时,该注解会自动生效。
@EnableAutoConfiguration 的主要功能是启动 Spring 应用程序上下文时进行自动配置,它会尝试猜测并配置你可能需要的 Bean。自动配置通常是基于项目 classpath 中引入的类和已定义的 Bean 来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的 jar 包中。
举个例子:如果将tomcat-embedded.jar添加到 classpath 下,那么@EnableAutoConfiguration 会认为你准备使用 TomcatServletWebServerFactory 类,并帮你初始化相关配置。与此同时如果自定义了基于 ServletWebServerFactory 的 Bean,那么,它将不会进行 TomcatServletWebServerFactory 类的初始化。这一系列的操作判断,都由 Spring Boot 来完成。
下面我们来看一下 @EnableAutoConfiguration 注解的源代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// 用来覆盖配置来开启/关闭自动配置的功能
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// 根据类(Class)排除指定的自动配置
Class<?>[] exclude() default {};
// 根据类名排除指定的自动配置
String[] excludeName() default {};
}
该注解提供了一个常量和两个成员参数的定义:
ENABLED_OVERRIDE_PROPERTY
:可以用来覆盖配置来开启/关闭自动配置的功能。
exclude
:根据类(Class)排除指定的自动配置
excludeName
:根据类名排除指定的自动配置。
正如上面所说,@EnableAutoConfiguration 会猜测你所需要使用的 Bean,但如果在实战中你并不需要它预置初始化的 Bean,则可通过该注解的 exclude 或 excludeName 参数来进行有针对性的排除。
比如,当不需要数据库的自动配置时,可通过以下两种方式让其自动配置失效:
// 通过@SpringBootApplication排除DataSourceAutoConfiguration
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
publicclassSpringLearnApplication {
}
或:
// 通过@EnableAutoConfiguration排除DataSourceAutoConfiguration
@Configuration
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
publicclassDemoConfiguration {
}
需要注意的是,被 @EnableAutoConfiguration 注解的类所在 package 还具有特定的意义,通常会被作为扫描注解 @Entity 的根路径。这也是为什么在使用 @SpringBootApplication 注解时需要将被注解的类放在顶级 package 下的原因,如果放在较低层级,则它所在 package 的同级或上级中的类则无法被扫描到。
而对于入口类和其 main 方法来说,并不依赖 @SpringBootApplication 注解或 @EnableAutoConfiguration ,也就是说该注解可以使用在其他类上,而非入口类上,同样生效。
@EnableAutoConfiguration 的关键功能是通过 @Import 注解导入的 ImportSelector 来完成的。从源码得知 @Import(AutoConfigurationImportSelector.class) 是@EnableAutoConfiguration 注解的组成部分,也是自动配置功能的核心实现者。
它又可以分为两部分:
@Import 和对应的 ImportSelector 。本节重点讲解 @Import 的基本使用和 ImportSelector 的实现类 AutoConfigurationImportSelector 。
下面,通过一张流程图来从整体上了解 AutoConfigurationImportSelector 功能及流程图。
当 AutoConfigurationImportSelector 被 @Import 注解引入之后,它的 selectImports 方法会被调用并执行其实现的自动装配逻辑。读者朋友需注意的 selectImports 方法几乎涵盖了组件自动装配的所有处理逻辑。
通过上图从整体上了解了 AutoConfigurationImportSelector 的概况及操作流程,关于 AutoConfigurationImportSelector 的详细实现可参考 Spring Boot 中的源码进行逐步分析,本篇文章就不再进行过多拓展。
@SpringBootApplication包含的三个注解及其含义
第一个:@SpringBootConfiguration(在这个类的源码中又有一个Configuration的注解)
@Configuration这个注解的作用就是声明当前类是一个配置类,然后Spring会自动扫描到添加了@Configuration的类,读取其中的配置信息,而@SpringBootConfiguration是来声明当前类是SpringBoot应用的配置类,项目中只能有一个。所以一般我们无需自己添加。
第二个:@EnableAutoConfiguration
开启自动配置,告诉SpringBoot基于所添加的依赖,去“猜测”你想要如何配置Spring。比如我们引入了spring-boot-starter-web,而这个启动器中帮我们添加了tomcat、SpringMVC的依赖,此时自动配置就知道你是要开发一个web应用,所以就帮你完成了web及SpringMVC的默认配置了!我们使用SpringBoot构建一个项目,只需要引入所需框架的依赖,配置就可以交给SpringBoot处理了。
第三个:@ComponentScan
配置组件扫描的指令
解释一:
提供了类似与context:component-scan标签的作用,通过basePackageClasses或者basePackages属性来指定要扫描的包。
如果没有指定这些属性,那么将从声明这个注解的类所在的包开始,扫描该包及其子包。而我们的@SpringBootApplication注解声明的类就是main函数所在的启动类,
因此扫描的包是该类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中。
解释二:
1、@ComponentScan这个注解在Spring中很重要,它对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
2、@ComponentScan告诉Spring 哪个packages 的用注解标识的类 会被spring自动扫描并且装入bean容器。
例如,如果你有个类用@Controller注解标识了,那么,如果不加上@ComponentScan,自动扫描该controller,那么该Controller就不会被spring扫描到,更不会装入spring容器中,因此你配置的这个Controller也没有意义。
本篇文章围绕 Spring Boot 的核心功能展开,带大家从整体上了解 Spring Boot 自动配置的原理以及自动配置核心组件的运作过程。只有掌握了这些基础的组建及其功能,在后续集成其他三方类库的自动配置时才能够更加清晰的了解它们都运用了自动配置的哪些功能。
转载:
https://baijiahao.baidu.com/s?id=1673715249485310741&wfr=spider&for=pc
https://www.jianshu.com/p/b2ade1737271
https://blog.csdn.net/qq_28289405/article/details/81302498