文章目录
SpringBoot自动装配
Spring全称SpringFramework也就是Spring框架,是目前最流行的Java框架,它可以帮助我们更加快速、更加简单的构建Java项目,而Spring家族中提供了许多框架,家族中的所有框架都是基于核心框架SpringFramework的。
而现在通过Spring开发也会比较繁琐,主要体现在两个地方:
- 在pom.xml中依赖配置比较繁琐,在项目开发时,需要自己去找到对应的依赖,还需要找到依赖它所配套的依赖以及对应版本,否则就会出现版本冲突问题。
- 在使用Spring框架进行项目开发时,需要在Spring的配置文件中做大量的配置,这就造成Spring框架入门难度较大,学习成本较高。
因为这些问题,Spring官方就提出一个全新的框架SpringBoot来简化Spring开发,直接通过SpringBoot来构建项目会比Spring更加简单快速,因为SpingBoot不用手动声明Bean对象,添加pom.xml依赖。
SpringBoot框架之所以使用起来更简单更快捷,是因为SpringBoot框架底层提供了两个非常重要的功能:一个是起步依赖,一个是自动配置。
1. 起步依赖
假设我们使用传统的Spring进行开发,此时就需要手动引入web开发的一些依赖,还要注意版本的兼容问题
<!-- servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring上下文 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<!-- Spring对象管理模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.20</version>
</dependency>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.17</version>
</dependency>
<!-- Mysql驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
而如果我们使用了SpringBoot,就不需要像上面这么繁琐的引入依赖了。我们只需要引入一个依赖就可以了,那就是web开发的起步依赖:springboot-starter-web
。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
为什么我们只需要引入一个web开发的起步依赖,web开发所需要的所有的依赖都有了呢?
就是因为Maven的依赖传递,将起步依赖所依赖的Jar包都导入了进来。比如说A依赖B,B依赖C,引入A之后C也会引入进来
2. 自动配置
SpingBoot自动配置就是当Spring容器启动后,一些配置类、bean对象就自动存入到IOC容器中,不需要我们手动去使用xml声明Bean对象。
这些对象在启动的时候就被存放到Spring容器中了,通过@Autowired
就可以直接注入进来。
@Autowired
private ObjectMapper objectMapper;
自动配置常见方案
我们知道通过@Component
修改的类会被存入Spring容器中,当需要注意的是Spring的扫描级别是类扫描,并且扫描的是被@SpringBootApplication
修饰的启动类同一级目录或者子目录中的类型。当我们想要引入第三方依赖的时候,如果不在同一个目录就需要指定要扫描的类,有那么几种常见的方案:
- 方案1:@ComponentScan 组件扫描
- 方案2:@Import 导入(使用@Import导入的类会被Spring加载到IOC容器中)
@ComponentScan组件扫描
通过@ComponentScan
注解来指定要扫描指定包下的类,需要注意的是如果使用该注解会覆盖原来的扫描路径,也就是说还要把当前启动类的路径也添加。
@SpringBootApplication
@ComponentScan({"com.test","com.example"}) //指定要扫描的包
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(HmDianPingApplication.class, args);
}
}
通过这种方式来完成自动配置,如果在开发的时候需要引入大量的第三方依赖,就需要在启动类上配置非常多的需要扫描的包,就会比较麻烦,且大量的包扫描性能较低。
所以SpringBoot并没有采用这种方式。
@Import导入
导入形式主要有以下几种:
- 导入普通类
- 导入配置类
- 导入ImportSelector接口实现类
-
使用@Import导入普通类
@SpringBootApplication @Import(AppConfig.class) //导入的类会被Spring加载到IOC容器中 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(HmDianPingApplication.class, args); } }
-
使用@Import导入配置类
配置类:
@Configuration public class HeaderConfig { @Bean public HeaderParser headerParser(){ return new HeaderParser(); } @Bean public HeaderGenerator headerGenerator(){ return new HeaderGenerator(); } }
启动类
@SpringBootApplication @Import(HeaderConfig.class) //导入配置类 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(HmDianPingApplication.class, args); } }
-
使用@Import导入ImportSelector接口实现类:
ImportSelector接口实现类,重写了ImportSelector的selectImports,该方法返回的String数组里的元素就是要导入到Spring容器中的类的全路径。
public class MyImportSelector implements ImportSelector { public String[] selectImports(AnnotationMetadata importingClassMetadata) { //返回值字符串数组(数组中封装了全限定名称的类) return new String[]{"com.example.HeaderConfig"}; } }
启动类:
@SpringBootApplication @Import(MyImportSelector.class) //导入ImportSelector接口实现类 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(HmDianPingApplication.class, args); } }
虽然使用@Import
注解通过这三种方式都可以导入第三方依赖中所提供的bean或者是配置类。
如果使用这种方式完成自动配置吗,当要引入一个第三方依赖的时候,那就还要知道第三方依赖中有哪些配置类和Bean对象,这些方法其实并不友好。
而依赖中有哪些配置类和bean第三方依赖是最清楚的,所以我们不用自己指定要导入哪些bean对象和配置类了,让第三方依赖它自己来指定
第三方依赖提供注解
比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以@EnableXxxx开头的注解,注解中封装的就是@Import注解。
第三方依赖中提供的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类
public @interface EnableHeaderConfig {
}
在使用的时候只需要在启动类上加上第三方依赖提供的@EnableXxxxx注解即可
@EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
以上四种方式都可以完成导入操作,但是第4种方式会更方便更优雅,而这种方式也是SpringBoot当中所采用的方式。
@SpringBootApplication
@SpringBootApplication
是SpringBoot启动类上的核心注解,源码如下:
前四个注解是Java中提供的元注解:
- @Target():指定注解能使用位置,这里表示在类、接口、枚举上
- @Retention:指定注解的声明周期,这里表示可以在运行的时候通过反射访问到
- @Documented:注解表明该自定义注解应该被包含在由Javadoc工具生成的文档中
- @Inherited:该注解表示当前类的子类能获取到其注解
@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}
)}
)
@SpringBootConfiguration注解
该注解的源码如下,关键信息就是@Configuration
,说明当前类也是一个配置类,也就是SpringBoot的启动类也是一个配置类。
@Indexed
注解是用来加速应用启动的
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@ComponentScan注解
前面已经提到这个注解是用来扫描指定包下的类到SpringIOC容器中
@EnableAutoConfiguration注解
该注解也是自动配置的核心注解,这个注解底层使用的是@Import
注解,导入了AutoConfigurationImportSelector
这个类
而AutoConfigurationImportSelector
这个类实现了DeferredImportSelector
这个接口,而DeferredImportSelector
这个接口拓展的就是ImportSelector
接口
而ImportSelector
接口中的selectImports方法返回的**String[]**数组里面存放的就是需要导入SpringIOC容器中的类的全路径名。
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
而AutoConfigurationImportSelector
这个类中就重写了selectImports
方法,源码如下:
selectImports()
方法底层调用getAutoConfigurationEntry()
方法,获取可自动配置的配置类信息集合
getAutoConfigurationEntry()``方法通过调用getCandidateConfigurations(annotationMetadata, attributes)
方法获取在配置文件中配置的所有自动配置类的集合,源码如下:
getCandidateConfigurations方法的功能:
获取META-INF/spring.factories以及
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中配置类的集合
这两个文件位于org.springframework.boot.autoconfigure
包下
那么这些配置文件这么多类都会导入到Spring的IOC容器中吗?
并不是。 在声明bean对象时,上面有加一个以@Conditional开头的注解,这种注解的作用就是按照条件进行装配,只有满足条件之后,才会将bean注册到Spring的IOC容器中
需要注意的是在SpringBoot在2.7以后,新增的org.springframework.boot.autoconfigure.AutoConfiguration.imports
这个配置文件,且在SpringBoot3之后只使用该文件
@Conditional
我们在跟踪SpringBoot自动配置的源码的时候,在自动配置类声明bean的时候,除了在方法上加了一个@Bean注解以外,还会经常用到一个注解,就是以Conditional开头的这一类的注解。以Conditional开头的这些注解都是条件装配的注解。下面我们就来介绍下条件装配注解。
@Conditional注解:
- 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。
- 位置:方法、类
- @Conditional本身是一个父注解,派生出大量的子注解:
@ConditionalOnClass
:判断环境中有对应字节码文件,才注册bean到IOC容器。@ConditionalOnMissingBean
:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。@ConditionalOnProperty
:判断Spring的配置文件中有对应属性和值,才注册bean到IOC容器。
所以@EnableAutoConfiguration
这个注解才是自动配置的核心
- 它封装了一个@Import注解,Import注解里面指定了一个ImportSelector接口的实现类。
- 在这个实现类中,重写了ImportSelector接口中的selectImports()方法。
- 而selectImports()方法中会去读取两份配置文件,并将配置文件中定义的配置类做为selectImports()方法的返回值返回,返回值代表的就是需要将哪些类交给Spring的IOC容器进行管理。
- 并且这两份配置文件的类并不一定会加载到IOC容器中,通过@Conditional开头的注解来让SpringBoot进行条件装配。