
目录
SpringBoot自动配置
Spring加载Bean
代码示例
原因分析
解决方案
@ComponentScan
@Import
结合源码解析
总结
SpringBoot自动配置
SpringBoot的⾃动配置就是当Spring容器启动后, ⼀些配置类, bean对象等就⾃动存⼊到了IoC容器中,不需要我们⼿动去声明, 从⽽简化了开发, 省去了繁琐的配置操作. SpringBoot⾃动配置, 就是指SpringBoot是如何将依赖jar包中的配置类以及Bean加载到Spring IoC容器中的.
Spring加载Bean
代码示例
在src/main/java路径下创建一个目录mTest,并创建一个类TestConfig
package mTest;
import org.springframework.stereotype.Component;
@Component
public class TestConfig {
public void use(){
System.out.println("执行BiteConfig#use….");
}
}
然后生成测试用例,进行测试
package springprinciple;
import mTest.TestConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import springprinciple.component.BeanLifeComponent;
import springprinciple.model.Dog;
@SpringBootTest
class SpringPrincipleApplicationTests {
@Autowired
private ApplicationContext context;
@Test
void testAutoConfg(){
TestConfig bean = context.getBean(TestConfig.class);
bean.use();
}
}
运行结果:

报错信息显示没有找到bean,明明我们给TestConfig类加上了@Compont注解,这是为什么呢?
原因分析
Spring通过五⼤注解和 @Bean 注解可以帮助我们把Bean加载到SpringIoC容器中, 以上有个前提就是这些注解类需要和SpringBoot启动类在同⼀个⽬录下 ( @SpringBootApplication 标注的类 就 是SpringBoot项⽬的启动类)。
因为上面的代码中SpringPrincipleApplication启动类所在目录为src/main/java/springprinciple,而TestConfig类所在目录为src/main/java/mTest,所以SpringBoot没有扫描到。
又比如当我们引⼊第三⽅的Jar包时, 第三⽅的Jar代码⽬录肯定不在启动类的⽬录下, 如何告诉Spring帮我们管理这些Bean呢?
解决方案
我们需要指定路径或者引⼊的⽂件, 告诉Spring, 让Spring进⾏扫描到. 常⻅的解决⽅案有两种: 1. @ComponentScan 组件扫描 2. @Import 导⼊(使⽤@Import导⼊的类会被Spring加载到IoC容器中)
@ComponentScan
通过 @ComponentScan 注解, 指定Spring扫描路径:
SpringPrincipleApplication
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("mTest")
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
运行结果:

@Import
@Import 导⼊主要有以下⼏种形式:
1. 导⼊类 2. 导⼊ ImportSelector 接⼝实现类
@Import源码

1.导入类
SpringPrincipleApplication
import mTest.TestConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
@Import(TestConfig.class)
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
上面我们看到@Import源码的内容是一个数组,即我们可以@Import多个类。
@Import多个类
TestConfig
import org.springframework.stereotype.Component;
@Component
public class TestConfig {
public void use(){
System.out.println("执行TestConfig#use….");
}
}
TestConfig2
import org.springframework.stereotype.Component;
@Component
public class TestConfig2 {
public void use(){
System.out.println("执行TestConfig2#use….");
}
}
SpringPrincipleApplication
import mTest.TestConfig;
import mTest.TestConfig2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
@Import({TestConfig.class,TestConfig2.class})
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
SpringPrincipleApplicationTests
import mTest.TestConfig;
import mTest.TestConfig2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import springprinciple.component.BeanLifeComponent;
import springprinciple.model.Dog;
@SpringBootTest
class SpringPrincipleApplicationTests {
@Autowired
private ApplicationContext context;
@Test
void testAutoConfg(){
TestConfig bean = context.getBean(TestConfig.class);
bean.use();
TestConfig2 bean2 = context.getBean(TestConfig2.class);
bean2.use();
}
}
运行结果:

2. 导⼊ ImportSelector 接⼝实现类
MyImportSelect
package mTest;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelect implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
"mTest.TestConfig",
"mTest.TestConfig2"
};
}
}
SpringPrincipleApplication
import mTest.MyImportSelect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
@Import(MyImportSelect.class)
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
运行结果:

但是他们都有⼀个明显的问题, 就是使⽤者需要知道第三⽅依赖中有哪些Bean对象或配置类. 如果漏掉其中⼀些Bean, 很可能导致我们的项⽬出现⼤的事故. 这对程序员来说⾮常不友好. 依赖中有哪些Bean, 使⽤时需要配置哪些bean, 第三⽅依赖最清楚, 那能否由第三⽅依赖来做这件事呢? ⽐较常⻅的⽅案就是第三⽅依赖给我们提供⼀个注解, 这个注解⼀般都以@EnableXxxx开头的注解, 注解中封装的就是 @Import 注解
1. 第三⽅依赖提供注解
EnableAutoConfig
package mTest;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelect.class)
public @interface EnableAutoConfig {
}
SpringPrincipleApplication
import mTest.EnableAutoConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableAutoConfig
@SpringBootApplication
public class SpringPrincipleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringPrincipleApplication.class, args);
}
}
运行结果:

结合源码解析

像我们使用@Autowired注入Application,在我们的代码中并没有实现这个类,也更没有显示交给Spring管理这个类,那为什么运行没有报错呢?是因为SpringBoot的启动类帮我们做了。
被@SpringBootApplication标注的类就是SpringBoot的启动类,下面我们仔细看看@SpringBootApplication这个类:

@SpringBootApplication 是⼀个组合注解, 注解中包含了: 1. 元注解 JDK中提供了4个标准的⽤来对注解类型进⾏注解的注解类, 我们称之为 meta-annotation(元注 解), 他们分别是:
• @Target 描述注解的使⽤范围(即被修饰的注解可以⽤在什么地⽅) • @Retention 描述注解保留的时间范围 • @Documented 描述在使⽤ javadoc ⼯具为类⽣成帮助⽂档时是否要保留其注解信息 • @Inherited 使被它修饰的注解具有继承性(如果某个类使⽤了被@Inherited修饰的注解,则 其⼦类将⾃动具有该注解)
2. @SpringBootConfiguration ⾥⾯就是@Configuration, 标注当前类为配置类, 其实只是做了⼀层封装改了个名字⽽已. (@Indexed注解,是⽤来加速应⽤启动的, 不⽤关心)。

3. @ComponentScan (包扫描) 可以通过 basePackageClasses 或 basePackages 来定义要扫描的特定包, 如果没有定义 特定的包, 将从声明该注解的类的包开始扫描, 这也是为什么SpringBoot项⽬声明的注解类必须要在 启动类的⽬录下. excludeFilters ⾃定义过滤器, 通常⽤于排除⼀些类, 注解等。
4. @EnableAutoConfiguration (开启⾃动配置) Spring⾃动配置的核⼼注解

主要看上面注解的两部分:
1. @Import({AutoConfigurationImportSelector.class}) 使⽤@Import注解,导⼊了实现ImportSelector接⼝的实现类.

selectImports() ⽅法底层调⽤ getAutoConfigurationEntry() ⽅法, 获取可⾃动配置的配置类信息集合.

getAutoConfigurationEntry() ⽅法通过调⽤getCandidateConfigurations(annotationMetadata, attributes) ⽅法获取在配置⽂件中配置的所有⾃动配置类的集合 。
2.@AutoConfigurationPackage

这个注解主要是导⼊⼀个配置⽂件 AutoConfigurationPackages.Registrar.class 。

Registrar实现了 ImportBeanDefinitionRegistrar 类, 就可以被注解@Import导⼊到spring 容器⾥。 (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]) : 当前启动类所在的包名。 所以,@AutoConfigurationPackage 就是将启动类所在的包下⾯所有的组件都扫描注冊到 spring 容器中。
总结
SpringBoot ⾃动配置原理的⼤概流程如下:

当SpringBoot程序启动时, 会加载配置⽂件当中所定义的配置类, 通过 @Import 注解将这些配置类全部加载到Spring的IOC容器中, 交给IOC容器管理.




