SpringBoot 自动装配
SpringBoot 自动装配就是是拆箱即用。比如mybatis,redis 等,不再需要以前那种繁琐的配置,直接引入starter包即可。
starter 区别
现在有很多starter组件,大致分为2种,比如
- spring-boot-starter-data-redis
- mybatis-spring-boot-starter
名称上面可以大致看出一些端倪,spring-boot-starter-xxx 是springboot官方出的一个包,而xxx-spring-boot-starter,是三方自己出的一个springboot的包。都可以完成自动装配的效果,但是在具体的实现上有着细微的差别。
SpringBoot 为什么可以这么方便
在我司的工作中,也会需要打出一个包,让其他业务方来引用,但是在引用过程中,有些人喜欢在xml中配置,或者指定spring的扫描路径到包中,来完成bean的注册和注入。假设,此处可以引用springboot的自动装配机制,那么会方便很多。说的直白一点,无非是解决了如下的2个问题:
- 如何在未知bean的包路径时,把bean注入
- bean与当前项目中有冲突怎么办
首先来看在SpringBoot中如何解决这件事情,@SpringBootApplication 是一个复合注解,包含了很多其他的注解,如下:
@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 {
....
可以看到,@SpringBootApplication = @ComponentScan + @EnableAutoConfiguration +@SpringBootConfiguration
@ComponentScan 包的扫描路径
@SpringBootConfiguration 等于@Configuration,只是换了一个名字
@EnableAutoConfiguration 自动装配,一旦加上此注解,那么将会开启自动装配功能
EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
EnableAutoConfiguration = @Import(AutoConfigurationImportSelector.class) + @AutoConfigurationPackage
AutoConfigurationPackage注解的作用是将 添加该注解的类所在的package 作为 自动配置package 进行管理,所以也就是说当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package。
@Import(AutoConfigurationImportSelector.class) @import注解, Import只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中。关于import的使用,可以点击这里。
AutoConfigurationImportSelector,中实现了ImportSelector接口,如下
// 返回的是,需要加载为bean的类全名。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
那么只需要在这个方法中,返回我们需要加入spring容易中的类名就好了。再看getAutoConfigurationEntry方法
// 方法内部使用了SpingBoot的spi方式去寻找三方包中的配置类
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取包中的配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
// 排出不需要的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
上诉中可以看到,自动装配是去获取对方包中的配置类,然后通过加载配置类来完成装配,同时还支持可以去除一些配置类
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
SpringFactoriesLoader.loadFactoryNames SPI 的写法,与jdk中ServiceLoader.load()类似,可以推断出,这个是属于spring的spi方式。同时也可以看到,是通过加载与EnableAutoConfiguration相关的spi。利用mybatis的例子来看,mybatis的包中,在meta-inf/spring.factories中如下
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
在使用中,springboot会加通过spi加载这2个AutoConfiguration类,同时mybatis便可以在这2个配置类中,完成一些自己需要的bean。这也是目前三方包,与springboot快速装配的方式。
之前讲到,包的引入分为官方包和三方包,官方包的引入稍微有点区别。比如redis,当springboot要引入redis时,org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,自身的拥有redis的配置文件,如下
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
...
@ConditionalOnClass(RedisOperations.class) 通过条件注册注解,可以防止在项目中未引入redis时也去加载此项配置。果然亲儿子就是不一样,同时,保证了,无论使用者在项目中引入spring-boot-starter-data-redis,还是直接引入redis,都可以完成此项自动装配,减少了开发者的工作。同时还有很多这样的条件注释,以后再分析。
总结
1)自动装配还是利用了SpringFactoriesLoader来加载META-INF/spring.factoires文件里所有配置的EnableAutoConfgruation,它会经过exclude和filter等操作,最终确定要装配的类
2)有一些配置,无需三方引入配置类,springboot自身带有它的配置,当条件满足时,便会完成注册。