Spring Boot自动装配原理详解

转载自:CSDN你就像甜甜的益达

@SpringBootApplication

这个注解是springboot的启动注解,配置了这个注解的方法就是springboot项目的入口; 一般springboot项目的启动类:

@SpringBootApplication
public class SpringBootPlusApplication {

  public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(SpringBootPlusApplication.class, args);
    // 打印项目信息
    PrintApplicationInfo.print(context);
  }

}

我们看看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) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {

}

@ComponentScan表示包扫描,FilterType.CUSTOM表示自定义扫描;可以看一下TypeExcludeFilter这类,实现TypeFilterl类,重写match方法进行判断是否加入spring容器中;这里不做过多详解,主要讲@SpringBootConfiguration、@EnableAutoConfiguration这两个注解;

@SpringBootConfiguration

源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

这个注解就一个@Configuration注解,就相当于把当前标注了@SpringBootConfiguration注解的类标记为配置类; @Configuration类里面有@Component注解…

@EnableAutoConfiguration

EnableAutoConfiguration这个注解从名字上面来看,这个注解应该是跟自动装配相关的注解了; 先看看EnableAutoConfiguration注解的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}

前四个是元注解,就不提了,后面两个@AutoConfigurationPackage和导入了入AutoConfigurationImportSelector这个类;

@AutoConfigurationPackage

这个类从名字上面看应该是自配配置包的: 先看看源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

这里主要导入了AutoConfigurationPackages.Registrar.class类:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		register(registry, new PackageImport(metadata).getPackageName());
	}

	@Override
	public Set<Object> determineImports(AnnotationMetadata metadata) {
		return Collections.singleton(new PackageImport(metadata));
	}

}

这个类获取到了使用这个注解的包路径,然后扫描注册对应包路径下所有组件,注册到spring容器中;

@Import(AutoConfigurationImportSelector.class)

先看AutoConfigurationImportSelector实现的接口:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}

实现的aware接口,就是在类中获取spring容器的组件,这里获取的是:beanFactory,environment,beanClassLoader,resourceLoader; 最主要的接口是DeferredImportSelector,这个接口继承了ImportSelector接口,

  • ImportSelector这个接口在调用地方是在,spring容器处理Import注解的时候,如果@import注解导入的类实现了ImportSelector接口,则会调用selectImports方法获取Import的类:

基于被引入的Configuration类的AnnotationMetadata信息选择并返回需要引入的类名列表; ImportSelector的实现和@import通常在处理方式上是一致的,可以在所以的配置类,就是@Configuration标记的类处理完成后在进行筛选;

  • DeferredImportSelector接口实现了ImportSelector接口,表明他们作用差不多,DeferredImportSelector接口在所有的@Configuration处理完成之后才会调用,在需要筛选的引入类型具备@Conditional(条件注入指定类的时候)非常有用;最后会执行selectImports方法;

AutoConfigurationImportSelector流程分析

DeferredImportSelector会通过getImportGroup返回执行的类,然后执行内部类AutoConfigurationGroup方法的process方法,然后执行getAutoConfigurationEntry方法,我们就从AutoConfigurationImportSelector的process开始: 通过process的getAutoConfigurationEntry来获取自动装配的Entry对象:

然后进到getCandidateConfigurations方法,getCandidateConfigurations就加载出了spring-boot-autuator-atoconfig包下META-INF\spring,factories文件,第一次的时候cache里面是空的,就会通过classloader加载META-INF/spring.factories文件,通过配置的key/vlaue保存到缓存中.给后面使用:

然后spring通过加载这些类,将这些配置类加入到spring容器中,可以看ConfigurationClassParser的processGroupImports方法,通过执行getImports方法,点进去就看见是执行DeferredImportSelector的selectImports方法;

这时候通过加载meta-inf文件夹下的spring.factories文件配置的配置类路径,进行加载对应的类,这时候自动装配就基本完成了,剩下的就是这些配置类可以通过@ConditionalOnBean等一系列注解来判断加不加载当前类: 举个例子:HttpEncodingAutoConfiguration类

可通过是否配置了spring.http.encoding来进行判断是否加载当前配置类…


// 20210519更新

@EnableAutoConfiguration作用

  1. 相当于两个注解@AutoConfigurationPackage、@Import({AutoConfigurationImportSelector.class})
  2. @AutoConfigurationPackage作用扫描使用了该注解的类所在包路径及子路径中@Component、@Controller、@Service等类,生成bean放入spring容器管理。
  3. @Import({AutoConfigurationImportSelector.class}) 作用是把AutoConfigurationImportSelector类引入容器管理,然后扫描所有添加的jar包中META-INFO/spring.factories下配置的enableautoconfigure,通过SPI将所有配置类初始化,创建bean

什么是SPI

通过 SpringFactoriesLoader 来读取配置文件 spring.factories 中的配置文件的这种方式是一种 SPI 的思想。那么什么是 SPI 呢?

SPI,Service Provider Interface。即:接口服务的提供者。就是说我们应该面向接口(抽象)编程,而不是面向具体的实现来编程,这样一旦我们需要切换到当前接口的其他实现就无需修改代码。

在 Java 中,数据库驱动就使用到了 SPI 技术,每次我们只需要引入数据库驱动就能被加载的原因就是因为使用了 SPI 技术。

打开 DriverManager 类,其初始化驱动的代码如下:

AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                            Iterator driversIterator = loadedDrivers.iterator();

                            try {
                                while(driversIterator.hasNext()) {
                                    driversIterator.next();
                                }
                            } catch (Throwable var4) {
                            }

                            return null;
                        }
                    });

内部LazyClassPathLookupIterator类会去扫描"META-INF/services/"下的文件就会发现,他会以接口名为key、实现类写在文件中,同理Spring Boot也是使用这样方式实现自动配置,在对应(我们需要使用的工具)jar包里,如果需要将配置引入spring容器,那么就可以这样配置

  • 在 resources 目录下新建一个文件 META-INF/spring.factories 文件,文件内新增一个如下配置:

添加入org.springframework.boot.autoconfigure.EnableAutoConfiguration = ?具体需要自动配置的类来完成

我们也可以去看其他第三方jar包很多都有这样的配置文件,供自动配

附:下面是一些condition条件的具体应用:

@Conditional扩展注解作用(判断是否满足当前指定条件)
@ConditionalOnJava系统的java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean
@ConditionalOnMissingBean容器中不存在指定Bean
@ConditionalOnExpression满足SpEL表达式指定
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定资源文件
@ConditionalOnWebApplication当前是web环境
@ConditionalOnNotWebApplication当前不是web环境
@ConditionalOnJndiJNDI存在指定项
end
  • 作者:Endwas(联系作者)
  • 发表时间:2021-02-01 13:42
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转博主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者名字和博客地址
  • 评论