揭秘SpringBoot自动装配的魔法

前言

首先,自动装配包含了哪些的装配?

  • 自动将包下的被打上@Component注解以及其派生注解的类作为Bean注册到IOC容器中
  • 自动注册jar包中需要被注册的Bean到IOC容器中

这篇文章将从底层源码分析,SpringBoot是如何做到自动装配Bean的。其中起到关键作用的两大关键注解是

而以上两大关键注解都元标注了@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 {
   
  // ...
}

我们通常会将被标注该注解的类注册到Spring容器中,之后,就会自动帮我们装配jar中配置的一些需要的Bean了。就像下面这样

@SpringBootApplication
public class UserServiceApplication {
   
  public static void main(String[] args) {
   
		// 将UserServiceApplication作为Bean注册到Spring中
    SpringApplication.run(UserServiceApplication.class, args);
  }
}

这样,就开启了自动挡,自动装配Bean到Spring中。

希望读者具有ConfigurationClassPostProcessor配置注解处理类的基础,才能更好的了解自动装配的魔法。

关于ConfigurationClassPostProcessor的分析可以查看 配置注解驱动的处理

自动装配的魔法

开头的分析就给了我们一个入口,也就是@EnableAutoConfiguration、@ComponentScan这两大注解,接下来我们就以这两个注解为入口,分析自动装配的魔法。

@ComponentScan扫描并注册

首先,来一个开胃小菜,这块内容相对比较简单,所以先来分析Bean的扫描并注册的过程。

我们知道,在SpringBoot中我们需要将引导类放在包的外层,然后那些@Service、@Controller等等@Component的派生注解都会被注册到Spring中,作为SpringBean。例如下面这样的结构
在这里插入图片描述
那么这是为什么呢?Spring解析配置注解驱动类ConfigurationClassPostProcessor负责@ComponentScan注解的解析工作,在解析时,由如下逻辑进行处理

其实这段逻辑在 配置注解驱动的处理 这篇文章中就已经讲述过,这里只做大致的描述。

// Process any @ComponentScan annotations
// 这里主要拿到@ComponentScan注解的元信息,里面包含注解中的属性值
// 如果是ComponentScans,也会分解成一个个的ComponentScan
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
  sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
// 从这里可以看出,这里也会进行@Conditional的条件过滤
// 并且阶段是REGISTER_BEAN,表示要开始注册Bean了
if (!componentScans.isEmpty() &&
    !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
   
  for (AnnotationAttributes componentScan : componentScans) {
   
    // The config class is annotated with @ComponentScan -> perform the scan immediately
    // 扫描并注册Bean
    Set<BeanDefinitionHolder> scannedBeanDefinitions =
      this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    // Check the set of scanned definitions for any further config classes and parse recursively if needed
    for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
   
      BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
      if (bdCand == null) {
   
        bdCand = holder.getBeanDefinition();
      }
      // 这里还会判断是否需要像配置类解析那样进行解析
      if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
   
        // 如果需要,则解析之,此方法就是刚开始的方法
        parse(bdCand.getBeanClassName(), holder.getBeanName());
      }
    }
  }
}

这里最为关键的是componentScanParser的parse方法,扫描并注册Bean

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
   
  // 主要扫描的类
  ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                                                                              componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

  // ...略过一些ComponentScan注解属性的配置
  
  Set<String> basePackages = new LinkedHashSet<>();
  // 获取basePackages属性值
  // 下面主要针对属性值,获取需要被扫描的包名
  String[] basePackagesArray = componentScan.getStringArray("basePackages");
  for (String pkg : basePackagesArray) {
   
    String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                                                           ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    Collections.addAll(basePackages, tokenized);
  }
  // 获取需要被注册的Class
  for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
   
    basePackages.add(ClassUtils.getPackageName(clazz));
  }
  //
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值