浅谈Springboot自动装配

Posted by klaus_turbo on 2020-11-21
Springboot

相关知识介绍

首先我们先来简单认识一下 @Import 这个注解。@Import注解支持将普通的
Java 类注入进容器里面,将其声明称一个 bean,可以将多个 Java Config 配置类融合称一个更大的 Config 类。
在 spring 4.2 之前,@Import 只支持导入配置类,但在 4.2 之后 @Import 注解支持导入普通的 Java 类,并将其声明称一个 bean。

使用方式

  1. 直接导入普通的 Java 类
  2. 配合自定义的 ImportSelector 使用
  3. 配合 ImportBeanDefinitionRegister 使用

直接导入普通 Java 类

我们先简单创建一个 Springboot 应用:

springboot_app.png

其中 Application 是主配置类,ImportHelloWorld 是我们的测试类。
看一下其中代码:

1
2
3
4
5
6
7
public class ImportHelloWorld implements InitializingBean {

@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化ImportHelloWorld...");
}
}

实现了 InitializingBean 用于在当前类被容器初始化之后做一些事情,在这里我们是直接打印了一句话。在主配置类中:

1
2
3
4
5
6
7
@Import(ImportHelloWorld.class)
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

我们直接使用 @Import 注解来将我们的 ImportHelloWorld 注入容器之内,来看一下启动后的控制台信息:

springboot_demo001.png

可以看到我们的注解方式生效了,并成功的将 bean 初始化之后的信息输出到了控制台上。

使用自定义的 ImportSelector

我们首先还是自定义一个 Java 类:

1
2
3
4
5
6
public class ImportSelectorHelloWorld implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化 ImportSelectorHelloWorld ...");
}
}

与上面同样的方式,使其可以在被加载之后可以输出一些标示性的话,其次是我们自定义一个我们自己的 ImportSelector 用于注册我们定义的 ImportSelectorHelloWorld :

1
2
3
4
5
6
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"xyz.turboklaus.spring.ImportSelectorHelloWorld"};
}
}

实现方法返回了一个 String 类型的数组,在数组中我们将定义好的 ImportSelectorHelloWorld 的包地址放进去,然后我们使用 @Import 将 MyImportSelector 注入容器 :

1
2
3
4
5
6
7
8
9
10
@Import({ImportHelloWorld.class,MyImportSelector.class})
@SpringBootApplication
public class Application {

@Autowired
private HelloWorldProperties properties;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

启动后控制台的输出信息:

springboot_demo002.png
成功的输出了我们定义的信息。

配合自定义的 ImportBeanDefinitionRegistrar

不多说,直接上代码:

1
2
3
4
5
6
7
// 自定义 Java 类
public class ImportBeanDefinitionRegistryHelloWorld implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化 ImportBeanDefinitionRegistryHelloWorld ... ");
}
}
1
2
3
4
5
6
7
8
// 自定义 ImportBeanDefinitionRegistrar
public class MyImportBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition definition = new RootBeanDefinition(ImportBeanDefinitionRegistryHelloWorld.class);
registry.registerBeanDefinition("importBeanDefinitionRegistryHelloWorld", definition);
}
}
1
2
3
4
5
6
7
8
// 注入 MyImportBeanDefinitionRegistry
@Import({ImportHelloWorld.class,MyImportSelector.class,MyImportBeanDefinitionRegistry.class})
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

来看运行结果:

springboot_demo003.png

成功输出。

主题 - - Springboot自动配置原理

@SpringbootApplication

一切的一切都要归根到这里,从 @SpringbootApplication 注解开始说起。

@SpringBootApplication 标注在某个类上说明:

  • 这个类是 Springboot 的主配置类
  • Springboot 就应该运行这个类的 main 方法来启动 Springboot应用

看一下这个注解的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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 是一个组合注解

  • @SpringBootConfiguration:该注解表示这是一个 SpringBoot 的配置类,其实它就是一个 @Configuration 注解
  • @ComponentScan:开启组件扫描
  • @EnableAutoConfiguration:从名字就可以看出来,就是这个类开启自动配置的。嗯,自动配置的奥秘全都在这个注解里面

@EnableAutoConfiguration

我们先来看一下它的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};
}

@AutoConfigurationPackage

从它的取名可以看出这大概是一个用于自动配置包的注解,我们继续看它的定义:

1
2
3
4
5
6
7
8
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};
}

终于我们看到了比较熟悉的一个注解 @Import 其中它注入了一个叫 Registrar 的类。我们在其方法上打上一个断点然后启动应用:

springboot_demo004.png

输出了我们主配置类所在包的包名。简而言之,@AutoConfigurationPackage 注解就是将主配置类所在包以及下面的所有子包里面的所有组件扫描到 Spring 容器之中,所以说,默认情况下,主配置类包以及子包之外的组件,Spring是扫描不到的。

@Import({AutoConfigurationImportSelector.class})

这个注解的作用就是给当前的配置类导入另外的别的自动配置类。@Import 注入的 AutoConfigurationImportSelector ,我们通过前面 @Import 的例子知道, selectimport 就是用来返回我们需要导入的组件的全类名的数组,就如同下面方法所示

1
2
3
4
5
6
7
8
9
   public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 1
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

看一下其大概的调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 2
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.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}


protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 3
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;
}

调用链大概如上1-2-3所示,其中第三步调用 SpringFactoriesLoader.loadFactoryNames 中比较关键的三步:

  • 从当前项目的类路径中获取所有 META-INF/spring.factories 这个文件下的信息。
  • 将上面获取到的信息封装成一个 Map 返回。
  • 从返回的 Map 中通过刚才传入的 EnableAutoConfiguration.class 参数,获取该 key 下的所有值。

其中的第一步就是将类路径下 META-INF/spring.factories 里面配置的所有 EnableAutoConfiguration 的值加入到 Spring 容器中。

至此Springboot的自动配置就完成了,其比较重要的一个文件就是 META-INF/spring.factories 。

本人关于图片作品版权的声明:

  1. 本人在此刊载的原创作品,其版权归属本人所有。

  2. 任何传统媒体、商业公司或其他网站未经本人的授权许可,不得擅自从本人转载、转贴或者以任何其他方式复制、使用上述作品。

  3. 传统媒体、商业公司或其他网站对上述作品的任何使用,均须事先与本人联系。

  4. 对于侵犯本人的合法权益的公司、媒体、网站和人员,本人聘请的律师受本人的委托,将采取必要的措施,通过包括法律诉讼在内的途径来维护本人的合法权益。

特此声明,敬请合作。