SpringBoot 自动装配

328 阅读4分钟

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自身带有它的配置,当条件满足时,便会完成注册。