国庆期间闲来无事,写了一个简单的小程序,小程序名称叫做 IT藏经楼。目的是分享这些年自己积累的一些学习材料,方面大家查找使用,包括电子书、案例项目、学习视频、面试题和一些PPT模板。里面所有材料都免费分享。目前小程序中只发布了非常小的一部分,后续会陆续上传分享。当前版本的小程序页面也比较简单,还在逐渐的优化中。
Spring Boot不得不说的一个特点就是自动装配,它是starter的基础,也是spring boot的核心,那到底什么是自动装配呢?
简单的说,就是自动将Bean装配到IoC容器中。接下来,我们通过一个例子来了解下自动装配。
- 添加starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在application.properties中添加Redis的配置
spring.redis.host=localhost
spring.redis.port=6379
- 在Controller中使用redisTemplate对Redis进行操作
@RestController
public class RedisController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/test")
public String test() {
redisTemplate.opsForValue().set("test", "test demo");
return "Test Demo";
}
}
在上面例子中,我们并没有通过XML形式或者注解形式把RedisTemplate注入到IoC容器中,但是在RedisController中却可以直接使用@Autowired来注入redisTemplate实例,这就表明IoC容器中已经存在RedisTemplate实例了,这就是Spring Boot自动装配机制。
自动装配的实现
自动装配在Spring boot中是通过@EnableAutoConfiguration注解来开启的,这个注解是在启动类注解@SpringBootApplication中声明的。
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication,run(SpringBootDemoApplication.class, args);
}
}
查看@SpringBootApplication注解,可以看到@EnableAutoConfiguration注解的声明
@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{
其实spring 3.1版本就已经开始支持@Enable注解了,它的主要作用是把相关组件的Bean装配到IoC容器中。@Enable注解对JavaConfig的进一步完善,使开发者减少了配置代码量,降低了使用难度,比较常见的Enable注解有@EnableWebMvc,@EnableScheduling等。
如果我们要基于JavaConfig的形式来完成Bean的装载,则必须要使用@注解及@Bean注解。@Enable注解本质上就是对这两种注解的封装,在@Enable注解中,一般都会带有一个@Import注解,比如@EnableScheduling注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}
所以,使用@Enable注解后,Spring会解析到@Import导入的配置类,并且根据这个配置类中的描述来实现Bean的装配。
EnableAutoConfiguration注解
当我们查看@EnableAutoConfiguration这个注解的时候,可以看到除了@Import注解之外,还有一个@AutoConfigurationPackage注解,而且@Import注解导入的并不是一个Configuration的配置类,而是AutoConfigurationImportSelector类,那AutoConfigurationImportSelector里面包含什么东西呢?
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}
AutoConfigurationImportSelector
AutoConfigurationImportSelector这个类实现了ImportSelector,它只有一个selectImports抽象方法,并且返回一个String数组,这个数组中的值就是需要装配到IoC容器中的类,当在@Import中导入一个ImportSelector的实现类后,会把该实现类中返回的class名称都装载到IoC容器中。
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
和@Confifguration不同的是,ImportSelector可以实现批量装配,而且可以通过逻辑处理来实现Bean的选择性装配,也就是可以根据上下文来决定哪些类可以被装配到IoC容器中,下面通过一个例子介绍下ImportSelector的使用:
- 首先创建两个类,我们要把这两个类装配到IoC容器中
public class FirstClass {
...
}
public class SecondClass {
...
}
- 创建ImportSelector的实现类,在实现类中把上面定义的两个类加入到数组中
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {FirstClass.class.getName(), SecondClass.class.getName()}
}
}
- 创建一个类似EnableAutoConfiguration的注解,通过@Import导入MyImportSelector
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({MyImportSelector.class})
public @interface MyAutoImport {
}
- 创建启动类,在启动类上使用MyAutoImport注解,然后通过ConfigurableApplicationContext获取FirstClass
@SpringBootApplication
@MyAutoImport
public class ImportSelectorDemo {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringBootApplication.run(ImportSelectorDemo.class);
FirstClass fc = context.getBean(FirstClass.class);
}
}
这种方式相比于@Import(*Configuration.class)的好处在于装配的灵活性,也可以实现批量装配。在MyImportSelector的String数组中可以定义多个Configuration类,一个配置类代表的就是某一个技术组件中批量的Bean的声明,所以在自动装配这个过程中只需要扫描到指定路径下对应的配置类即可。
自动装配原理
自动装配的核心是扫描约定目录下的文件进行解析,解析完成之后把得到的Configuration配置类通过ImportSelector进行导入,进而完成Bean的自动装配过程。
我们看一下AutoConfigurationImportSelector中的selectImports方法,它是ImportSelector接口的实现,该方法中主要做了两件事:
- AutoConfigurationMetadataLoader.loadMetadata方法从META-INF/spring-autoconfigure-metadata.properties文件中加载自动装配的条件元数据,也就是只有满足条件的Bean才会被装配
- autoConfigurationEntry.getConfigurations()方法收集所有符合条件的配置类,进行自动装配
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());
}
}
接下来我们了解下getAutoConfigurationEntry这个方法,这个方法会扫描指定路径下的文件进行解析,从而得到所需要装配的配置类,它主要做了下面几件事:
- getAttributes方法获得@EnableAutoConfiguration注解中的属性exclude、excludeName等。
- getCandidateConfiguration方法获得所有自动装配的配置类。
- removeDuplicates方法去掉重复的配置项。
- getExclusions方法根据@EnableAutoConfiguration注解中配置的exclude等属性,把不需要自动装配的配置类移除。
- fireAutoConfigurationImportEvents广播事件。
- 最后返回经过多层判断和过滤之后的配置类集合
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);
}
}
总的来说,它先获得所有的配置类,通过去重、exclude排除等操作,得到最终需要实现自动装配的配置类。其中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,它是Spring内部提供的一种约定俗成的加载方式,和Java的SPI类似。它会扫描classpath下的META-INF/spring.factories文件,spring.factories文件中的数据以key=value的形式存储,SpringFactoriesLoader.loadFactoryNames()会根据key的到对应的value值,因此,在自动装配这个场景中,key对应为EnableAutoConfiguration,value是多个配置类,也就是getCandidateConfigurations方法的返回值。
# 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,\
......
如果我们打开RabbitAutoConfiguration,可以看到它就是一个基于JavaConfig形式的配置类:
@Configuration
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties({RabbitProperties.class})
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration {
public RabbitAutoConfiguration() {
}
@Configuration
@ConditionalOnClass({RabbitMessagingTemplate.class})
@ConditionalOnMissingBean({RabbitMessagingTemplate.class})
@Import({RabbitAutoConfiguration.RabbitTemplateConfiguration.class})
protected static class MessagingTemplateConfiguration {
protected MessagingTemplateConfiguration() {
}
@Bean
@ConditionalOnSingleCandidate(RabbitTemplate.class)
public RabbitMessagingTemplate rabbitMessagingTemplate(RabbitTemplate rabbitTemplate) {
return new RabbitMessagingTemplate(rabbitTemplate);
}
......
}
除了@Configuration注解,还有一个@ConditionalOnClass注解,这个条件控制机制在这里的用途是判断classpath下是否存在RabbitTemplate和Channel这两个类,如果有,则把当前配置类注册到IoC容器中。@EnableConfigurationProperties是属性配置,我们可以按照约定在application.properties中配置RabbitMQ的参数,这些配置会加载到RabbitProperties中。
到此处,自动装配的工作流程就结束了,其实主要的核心过程是如下几点:
- 通过@Import(AutoConfigurationImportSelector)实现配置类的导入
- AutoConfigurationImportSelector类实现了ImportSelector接口,重写了方法selectImports,用于实现批量配置类的装载。
- 通过spring提供的SpringFactoriesLoader机制扫描classpath下META-INF/spring.factories文件,读取需要实现自动装配的配置类。
- 通过条件筛选,把不符合条件的配置类移除,最终完成自动装配。