SpingBoot 自动配置原理解析
SpringBoot框架可以对Spring以及SpringMVC进行自动配置,十分方便。最近研究了一下自动配置的原理,记录一下
1、SpringBoot启动类
SpringBoot有一个主配置类,即AutoconfigApplication类。SpringBoot在启动时会加载主配置类,开启了一些自动配置功能。
@SpringBootApplication
public class AutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(AutoconfigApplication.class, args);
}
}
我们跟踪进入@SpringBootApplication注解,其中会有一个@EnableAutoConfiguration注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
// 引入了一个AutoConfigurationImportSelector类
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@Import注解引入了一个AutoConfigurationImportSelector类,我们进入此类中,发现有如下方法:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
我们继续跟进this.getAutoConfigurationEntry()方法:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取候选得配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
接下来,进入到this.getCandidateConfigurations()方法中找到如下代码:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
我们摘出如下关键代码:
SpringFactoriesLoader.loadFactoryNames()
作用: 扫描所有jar包类路径下 META-INF/spring.factories,把扫描到的这些文件的内容包装成properties对象,从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中
截取部分spring.factories内容如下:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
到此为止便找到了我们想要得答案:
@EnableAutoConfiguration 注解会导入一个自动配置类AutoConfigurationImportSelector,而这个类又通过selectImports()方法扫描各个jar包路径下的 META-INF/spring.factories 并封装成相对应的properties对象,从中读取到EnableAutoConfiguration.class类所对应的值,即类的全限定类名,然后把他们添加进容器中,至此就完成了自动配置
自动配置案例
如果说上面的源码读的有些晦涩,可以通过下面的小案例来了解一下。
1、application.properties配置文件
在SpringBoot的配置文件中配置一下信息
student.name=张三
student.age=18
2、编写properties对应的JavaBean
@ConfigurationProperties(prefix = "student") //读取配置文件中前缀为student的配置与类中的属性相对应
public class StudentProperties {
private String name;
private Integer age;
@Override
public String toString() {
return "StudentConfig{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
3、编写自动配置类
@Configuration //声明这是一个配置类
@EnableConfigurationProperties(StudentProperties.class) // 读入配置对象
@ConditionalOnClass(StudentService.class) // 只有类路径存在时才会加载配置类
public class StudentAutoConfiguration {
@Autowired
private StudentProperties studentConfig;
@Bean
@ConditionalOnMissingBean(StudentService.class)
public StudentService returnStudentService(){
StudentService studentService = new StudentService();
studentService.setName(studentConfig.getName());
return studentService;
}
}
其中的@ConditionalOnClass注解的作用就相当与一个 if 的条件判断,作用就是当类路径存在时才会加载该配置类,如果不存在就跳过。
@ConditionalOnMissingBean(StudentService.class) 这个注解也是 SpringBoot 自动配置的核心所在,它的作用是: 判断如果 StudentService 不存在,就加载配置类,如果存在就跳过 ,这个注解就解释了为什么SpringBoot会优先使用用户自定义的配置类,如果用户没有自定义配置类,才会加载自动配置类。
4、使用自动配置类编写测试代码
@Controller
public class TestController {
@Autowired
StudentService studentService;
@RequestMapping("/getStudentName")
@ResponseBody
public String getName(){
return studentService.getName();
}
}
此时的运行结果如下图所示:
用于模拟SpringBoot自动配置了StudentService
5、自定义的用户配置类
@Component
public class StudentService {
private String name = "autoConfig";
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
}
定义自己的配置类加入容器中,并且给Name设置初值来区分,运行结果如下:
两个不同的结果就印证了SpringBoot会优先加载用户自定义的配置类,如果用户没有自定义配置类,才会加载自动配置类。