本节目标
- springboot为什么可以省略很多常用jar包的版本呢?
- 为什么能扫描启动类及其下面的注解类加入到spring容器中呢?
- 为什么可以简化springmvc以前那么多xml配置文件呢?
本节就是解决上面的疑惑,从整理上有个了解。虽然这些内容的了解对实际开发没有太大帮助,但是确能深入理解一下springboot的自动化配置的原理。对于以后更深入的开发--比如做自己的启动器有很大帮助。
依赖导入原理
依赖spring-boot-dependencies两种方式,在前两节介绍过了,这里汇总下:
-
继承父工程方式
xml<!--继承父项目方式--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.10</version> <relativePath/> <!-- lookup parent from repository --> </parent>
-
导入springboot 依赖方式
xml<dependencyManagement> <dependencies> <!--==================================== --> <!-- springboot 依赖 --> <!--==================================== --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.7.10</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
不管哪种方式,他们都是殊途同归的引入spring-boot-dependencies-版本.pom文件。也正是由于依赖这个pom文件,很多常用jar包的版本都定义好了,如下部分内容:
xml
<properties>
<activemq.version>5.16.6</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.98</appengine-sdk.version>
<artemis.version>2.19.1</artemis.version>
<aspectj.version>1.9.7</aspectj.version>
<assertj.version>3.22.0</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.2.0</awaitility.version>
<build-helper-maven-plugin.version>3.3.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.12.23</byte-buddy.version>
<cache2k.version>2.6.1.Final</cache2k.version>
<caffeine.version>2.9.3</caffeine.version>
<cassandra-driver.version>4.14.1</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
<commons-codec.version>1.15</commons-codec.version>
......
这里管理着springboot中所有依赖的版本,导入依赖如果不写版本就用这里的版本。
两个重要的注解
typescript
@SpringBootApplication
public class Springboot02Application {
public static void main(String[] args) {
//SpringApplication.run(Springboot02Application.class, args);
SpringApplication app = new SpringApplication(Springboot02Application.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
}
在我们的springboot启动类上加入该注解。这个注解说明该类为SpringBoot的主配置类。SpringBoot就应该运行该类的main方法来启动SpringBoot项目。进入这个注解,可以发现它是一个组合注解。
less
@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 {
其中包含两个非常重要的注解:
- @SpringBootConfiguration
- @EnableAutoConfiguration
@SpringBootConfiguration
css
@SpringBootConfiguration
在我们的springboot启动类上加入该注解。这个注解说明该类为SpringBoot的主配置类。SpringBoot就应该运行该类的main方法来启动SpringBoot项目。进入这个注解,可以发现它是一个组合注解,其中包含两个非常重要的
进入@SpringBootApplication注解,发现还有一个注解@Conguration,说明它本身就是个配置类。
less
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
进入@Conguration注解,发现它还有个注解@Component,说明它也是容器中的一个组件。
less
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@EnableAutoConfiguration
该注解为SpringBoot实现自动配置的核心注解。按照英文的字面意思:开启自动配置,也能联想到,有了这个注解我们之前需要配置的一大堆的xml配置文件,现在不需要配置了。进入该注解:
less
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
其中也有两个非常重要的注解:
- @AutoConfigurationPackage
- @Import(AutoConfigurationImportSelector.class)
下来详细看看这两个注解做的事情。
1.@AutoConfigurationPackage 自动配置包
css
@AutoConfigurationPackage
我们进入该注解,发现这个注解有一个@Import注解
less
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
@Import(AutoConfigurationPackages.Registrar.class)
这个@import有点类似我们以前在springMVC配置文件中的配置文件导入,回忆一下spring.xml:
xml
<!-- 导入redis的相关配置 -->
<import resource="classpath:spring/spring-redis.xml" />
它的作用和上面回忆中的意思差不多,就是给容器中导入一个组件,也就是导入 AutoConfigurationPackages.Registrar.class 的组件 ,那我们也进入 Registrar 类的内部看看
通过registerBeanDefinitions【注册很多Bean的定义】这个命名不难猜出,这里要把很多的bean注册到spring容器中,我们在register方法前加一个断点,看看这个注册类的父包是什么。这里可以debug进行调试观察,选中new PackageImports(metadata).getPackageNames()
右键选Evaluate Expression,如下图:
然后在弹出对话框中点击Evaluate
按钮:
总结: @AutoConfigurationPackag 的作用就是将SpringBoot 主配置类所在的包及其下面的所有子包里面的所有组件扫描到 Spring 容器中。这也就是为什么要求我们创建的所有类和包,最好在启动类的同目录或者子类目下了。
2.@Import(AutoConfigurationImportSelector.class)
python
@Import(AutoConfigurationImportSelector.class)
该注解的作用就是给容器中导入组件,这个注解需要导入的组件就是 AutoConfigurationImportSelector,也就是自动配置导入选择器,它可以帮我们选择需要导入的组件。进入该类,可以看到这么一个方法:
加上断点对 springboot02 进行debug启动,可以看到有144个配置类:
我们仅仅在项目中引入了一个web依赖:
xml
<!-- web项目相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
但是却又144个自动配置类自动加载了。这也就是为什么我们不需要进行繁琐的xml配置的根本原因了。自动配置类给我们当前项目的场景提供了一些组件和配置,有了自动配置就避免了手动编写配置文件,注入等等功能
在我们断点的下面有一句断言的代码:
arduino
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
大概意思是在META-INF/spring.factories或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中找不到配置类。这说明加载自动配置类就是从这两个地方找的。我们打开这autoConfiguration.imports看看:
可以看到里面有很多的配置类。意味着很多很多配置文件就设置好了。对应的默认值,也都设置好了。就好比默认加载如下配置文件
记忆印记
- 继承父工程或导入spring-boot-dependencies使我们不用关注常用依赖的版本号
- @SpringBootConfiguration:表明我们的springboot启动类也是一个配置类同时被spring容器管理
- @AutoConfigurationPackag:能扫描启动类及其下面的所有的子包
- AutoConfigurationImportSelector.class:加载了spring-boot-autoconfigure下所有的自动配置类