Spring @Component源码解析

一 注解声明Bean介绍

fb92ed19e175ae8fadcdeb5fd7d34efc.png

我们再回到这张图看,之前的两篇文章介绍了SpringIOC的源码(Spring容器(IOC)源码解析),但是我们是根据XML这个配置文件介绍的,在我们实际工作中目前来说用XML定义Bean的情况已经很少了,基本都是基于注解声明Bean定义,这种情况Bean是如何被注册到IOC容器的呢?我们围绕这个问题探讨。

我们从上图就可以看出来,XML解析和注解解析要走不同的分支,我们可以自己考虑一下XML是根据指定的XML文件地址,找到具体Bean声明文件,然后解析内部标签,根据标签属性给Bean配置不同属性或一些定义信息,之后转换成BeanDefinition对象由后续的IOC容器进行Bean的实例化和初始化;其实注解是同样的原理,只不过需要不同的逻辑去解析而已,首先我们要定义Bean的扫描包路径,Spring会把定义的扫描包路径所有类都拿到,然后判断是否使用了声明Bean注解,根据注解获取相应定义,转换成BeanDefinition对象,之后的流程和XML解析是一样的。

二 声明Bean注解都有什么

我们常用的声明Bean的注解有@Component  @Controller  @Service

@Repository  @Configuration。

这些声明Bean注解有什么不同呢,其实他们的核心都是@Component,他们内部目前来说是没有什么差异的,都是引用@Component,既然都是@Component为什么Spring要定义不同的注解,第一为了在不同场景和分层内有明确区分,方便使用者阅读;最核心的还是Spring的高扩展性,Spring在编码的任何阶段都充分考虑扩展性,比如我们都用@Component注解声明bean有问题吗,其实是没问题的,他们都是可以正常注册到IOC中的,如果某天Spring需要对Controller的Bean做一些特殊定义或逻辑的时候,那通过这一个注解就无法扩展了,所以Spring在外边包了一层,而@Component只做Bean声明定义,这样也说明Spring遵守编码单一性原则。我们日常的开发中也要注意此类问题保证类和方法的单一性原则。

三 声明Bean源码

通过源码来验证一下声明Bean的注解核心是不是都是@Component注解。

// @Controller注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {


  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  @AliasFor(annotation = Component.class)
  String value() default "";


}


// @Service注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {


  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  @AliasFor(annotation = Component.class)
  String value() default "";


}


// @Repository注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {


  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  @AliasFor(annotation = Component.class)
  String value() default "";


}


// @Configuration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {


  /**
   * Explicitly specify the name of the Spring bean definition associated with the
   * {@code @Configuration} class. If left unspecified (the common case), a bean
   * name will be automatically generated.
   * <p>The custom name applies only if the {@code @Configuration} class is picked
   * up via component scanning or supplied directly to an
   * {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
   * is registered as a traditional XML bean definition, the name/id of the bean
   * element will take precedence.
   * @return the explicit component name, if any (or empty String otherwise)
   * @see AnnotationBeanNameGenerator
   */
  @AliasFor(annotation = Component.class)
  String value() default "";


  /**
   * Specify whether {@code @Bean} methods should get proxied in order to enforce
   * bean lifecycle behavior, e.g. to return shared singleton bean instances even
   * in case of direct {@code @Bean} method calls in user code. This feature
   * requires method interception, implemented through a runtime-generated CGLIB
   * subclass which comes with limitations such as the configuration class and
   * its methods not being allowed to declare {@code final}.
   * <p>The default is {@code true}, allowing for 'inter-bean references' via direct
   * method calls within the configuration class as well as for external calls to
   * this configuration's {@code @Bean} methods, e.g. from another configuration class.
   * If this is not needed since each of this particular configuration's {@code @Bean}
   * methods is self-contained and designed as a plain factory method for container use,
   * switch this flag to {@code false} in order to avoid CGLIB subclass processing.
   * <p>Turning off bean method interception effectively processes {@code @Bean}
   * methods individually like when declared on non-{@code @Configuration} classes,
   * a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
   * behaviorally equivalent to removing the {@code @Configuration} stereotype.
   * @since 5.2
   */
  boolean proxyBeanMethods() default true;


}

通过源码可以看到他们的核心确实是@Component注解,而且内部属性定义等都是一模一样的,唯独@Configuration多了一个代理属性,这也就是刚刚说到的扩展性了。

Spring是通过扫描类上是否有@Component注解来注册Bean的,所以只要类上有@Component  Spring肯定会注册成Bean。

四 源码环境准备

还是在之前的项目定义一个声明注解Bean

348b501301aa14a162df53f32c2f6600.png

通过注解扫描包进行测试

e965c337d2480c7b013ec48a017e6de8.png

五 @Component源码解析

入口方法,初始化一些基础类;进行扫描解析成BeanDefinition对象;Spring的refresh方法,包括增强,bean实例化和初始化。

// 创建一个新的 AnnotationConfigApplicationContext,扫描给定包中的组件,为这些类注册 bean 定义,并自动刷新上下文
  public AnnotationConfigApplicationContext(String... basePackages) {
    // 初始化一些配置基础类
    this();
    // 注解扫描的核心,这一步已经把Bean注册好了BeanDefinition对象了
    // 这块是和XML不一样的地方,XML是在refresh()方法内解析,注解是在这一步解析
    scan(basePackages);
    // 和XML是一样的,注解和XML解析内部有一些差异,后边补充
    refresh();
  }

和XML入口对比的看下,其实第一步都是初始化基础类,第二步注解是扫描包,并注册成BeanDefinition对象,而XML是设置配置文件路径,第三步核心的都是调用的同一个方法,包括Beanfactory增强,bean实例化和初始化。

// 使用给定的父级创建一个新的 ClassPathXmlApplicationContext,从给定的 XML 文件加载定义。
  public ClassPathXmlApplicationContext(
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
      throws BeansException {
    // 构造器调用,一直到AbstractApplicationContext.class
    // 创建一个AbstractApplicationContext,合并父级配置到当前
    super(parent);
    // 设置配置文件路径
    setConfigLocations(configLocations);
    // refresh Spring容器
    // bean的创建和实例化都是在refresh()方法中进行的,refresh()函数中包含了几乎所有的ApplicationContext中提供的全部功能。
    if (refresh) {
      refresh();
    }
  }

接着进入扫描包方法

// 在指定的包内执行扫描
  @Override
  public void scan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
        .tag("packages", () -> Arrays.toString(basePackages));
    this.scanner.scan(basePackages);
    scanPackages.end();
  }


  // 在指定的包内执行扫描
  public int scan(String... basePackages) {
    // 获取当前IOC BeanDefinition数量
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();


    // 完成包扫描
    doScan(basePackages);


    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
      // 注册注释配置处理器,后置增强处理器
      AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }


    // 判断此次共新增多少BeanDefinition
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
  }

进行包扫描,注册bean定义,其实看到BeanDefinitionHolder我们就很熟悉了,这步已经注册成BeanDefinition对象,并可供SpringIOC进行实例化了。

// 在指定的基本包内执行扫描,返回注册的 bean 定义
  // 此方法不注册一个注解配置处理器而是让这件事给调用者
  protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    // 多个包遍历扫描
    for (String basePackage : basePackages) {
      // 扫描包下有Spring Component注解,并且生成BeanDefinition
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      // 封装BeanDefinitionHolder 并注册Spring容器
      for (BeanDefinition candidate : candidates) {
        // 设置scope,默认是singleton
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
        candidate.setScope(scopeMetadata.getScopeName());
        String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
        if (candidate instanceof AbstractBeanDefinition) {
          postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
        }
        if (candidate instanceof AnnotatedBeanDefinition) {
          AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
        }
        if (checkCandidate(beanName, candidate)) {
          BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
          // 生成代理类信息
          definitionHolder =
              AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
          beanDefinitions.add(definitionHolder);
          // 注册到Spring容器,这个看第一篇文章和XML注册是一样的,https://mp.weixin.qq.com/s/gYWfjPBrnyDJQ7CPTvkadw
          registerBeanDefinition(definitionHolder, this.registry);
        }
      }
    }
    return beanDefinitions;
  }

扫描包下的class文件,把有Component注解的封装BeanDefinition列表返回。

// 扫描包的类路径
  public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    // 带组件索引处理
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
      return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    // 一般情况进到这块进行扫描
    else {
      return scanCandidateComponents(basePackage);
    }
  }

继续进入scanCandidateComponents(basePackage)分析,将包路径转换为文件绝对路径进行扫描其下面所有*.class文件,并将其解析为自己的资源对象Resource,接着最终要一步,遍历筛选候选组件isCandidateComponent(metadataReader) ,在此操作之前将resource文件通过反射获取到类的元信息,后面进行注解判断需要

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
      // classpath下对应包下面的所有*.class文件,resolveBasePackage(basePackage) 方法将包名称转换为路径名称,拼接为文件绝对路径
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
          resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      // 文件解析为资源对象
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
      boolean traceEnabled = logger.isTraceEnabled();
      boolean debugEnabled = logger.isDebugEnabled();
      for (Resource resource : resources) {
        if (traceEnabled) {
          logger.trace("Scanning " + resource);
        }
        // 判断是否可读
        if (resource.isReadable()) {
          try {
            //此处通过反射获取类元信息
            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            // 进行过滤
            if (isCandidateComponent(metadataReader)) {
              ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
              sbd.setSource(resource);
              if (isCandidateComponent(sbd)) {
                if (debugEnabled) {
                  logger.debug("Identified candidate component class: " + resource);
                }
                candidates.add(sbd);
              }
              else {
                if (debugEnabled) {
                  logger.debug("Ignored because not a concrete top-level class: " + resource);
                }
              }
            }
            else {
              if (traceEnabled) {
                logger.trace("Ignored because not matching any filter: " + resource);
              }
            }
          }
          catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                "Failed to read candidate component class: " + resource, ex);
          }
        }
        else {
          if (traceEnabled) {
            logger.trace("Ignored because not readable: " + resource);
          }
        }
      }
    }
    catch (IOException ex) {
      throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
  }

获取类元信息

@Override
  public MetadataReader getMetadataReader(Resource resource) throws IOException {
    if (this.metadataReaderCache instanceof ConcurrentMap) {
      // No synchronization necessary...
      // 从本地缓存获取是否被创建过
      MetadataReader metadataReader = this.metadataReaderCache.get(resource);
      // 创建过则直接返回,否则进行创建
      if (metadataReader == null) {
        // 获取.class类元信息
        metadataReader = super.getMetadataReader(resource);
        // 存放到本地缓存
        this.metadataReaderCache.put(resource, metadataReader);
      }
      return metadataReader;
    }
    else if (this.metadataReaderCache != null) {
      synchronized (this.metadataReaderCache) {
        MetadataReader metadataReader = this.metadataReaderCache.get(resource);
        if (metadataReader == null) {
          metadataReader = super.getMetadataReader(resource);
          this.metadataReaderCache.put(resource, metadataReader);
        }
        return metadataReader;
      }
    }
    else {
      return super.getMetadataReader(resource);
    }
  }

获取类元信息

@Override
  public MetadataReader getMetadataReader(Resource resource) throws IOException {
    // 默认是SimpleMetadataReader实例
    return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
  }


  SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
    SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
    // 解析.class元信息
    getClassReader(resource).accept(visitor, PARSING_OPTIONS);
    this.resource = resource;
    this.annotationMetadata = visitor.getMetadata();
  }

在isCandidateComponent(metadataReader)方法里,做了两件事,第一件判断有没有excludeFilters注解(这种注解的bean不被注入),第二件是判断有没有includeFilters注解(这种注解就是@Component这种注解,可以进行注入),其实判断也很简单,返回true的成功候选者,spring 中AnnotationConfigApplicationContext容器只识别@Component和@ManageBean两个注解,这段最核心的部分tf.match(metadataReader, getMetadataReaderFactory())进行判断

// 确定给定的类是否不匹配任何排除过滤器,并且匹配至少一个包含过滤器
  protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        return false;
      }
    }
    for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        return isConditionMatch(metadataReader);
      }
    }
    return false;
  }

通过反射获取注解信息和出入的includeFilter注解进行判断

@Override
  public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
      throws IOException {


    // This method optimizes avoiding unnecessary creation of ClassReaders
    // as well as visiting over those readers.
    if (matchSelf(metadataReader)) {
      return true;
    }
    ClassMetadata metadata = metadataReader.getClassMetadata();
    if (matchClassName(metadata.getClassName())) {
      return true;
    }


    if (this.considerInherited) {
      String superClassName = metadata.getSuperClassName();
      if (superClassName != null) {
        // Optimization to avoid creating ClassReader for super class.
        Boolean superClassMatch = matchSuperClass(superClassName);
        if (superClassMatch != null) {
          if (superClassMatch.booleanValue()) {
            return true;
          }
        }
        else {
          // Need to read super class to determine a match...
          try {
            if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
              return true;
            }
          }
          catch (IOException ex) {
            if (logger.isDebugEnabled()) {
              logger.debug("Could not read super class [" + metadata.getSuperClassName() +
                  "] of type-filtered class [" + metadata.getClassName() + "]");
            }
          }
        }
      }
    }


    if (this.considerInterfaces) {
      for (String ifc : metadata.getInterfaceNames()) {
        // Optimization to avoid creating ClassReader for super class
        Boolean interfaceMatch = matchInterface(ifc);
        if (interfaceMatch != null) {
          if (interfaceMatch.booleanValue()) {
            return true;
          }
        }
        else {
          // Need to read interface to determine a match...
          try {
            if (match(ifc, metadataReaderFactory)) {
              return true;
            }
          }
          catch (IOException ex) {
            if (logger.isDebugEnabled()) {
              logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +
                  metadata.getClassName() + "]");
            }
          }
        }
      }
    }


    return false;
  }
@Override
  protected boolean matchSelf(MetadataReader metadataReader) {
    AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
    return metadata.hasAnnotation(this.annotationType.getName()) ||
        (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
  }

六 注解解析堆栈流程

a2ce109b9f661f31158b6ac789cd4f32.png

255bca10ad704f905cf0fce8b10765a2.png

七 refresh方法XML和注解解析的差别

XML主要是在obtainFreshBeanFactory()做XML的解析及bean注册,但是我们刚看到了注解其实是在scan(basePackages)方法里做的,所以这一步肯定不用再做了。

a7fb40b39e70db6b4db53e4f42f4e9ab.png

obtainFreshBeanFactory()方法里就2个方法,他们都是工厂,所以根据不同的工厂适配不同的具体实现。

5c068c07e819a3b69303062b44b38195.png

注解会进到GenericApplicationContext.class的refreshBeanFactory()和getBeanFactory()方法,此处只是把之前创建好的beanFactory返回。

aec959dbea0668ef20b2454a1091669d.png

1af1a13ba03a2d52dbd130c19b692217.png

而XML都会进去到AbstractRefreshableApplicationContext.class的这2个方法,XML解析可以查看之前的文章(Spring容器(IOC)源码解析(一)

八 UML类图

3360e22e2e34c9cee5c49a48f06dcfd1.png

九 总结

Spring注解bean注册过程。第一步,初始化时设置了Component类型过滤器;第二步,根据指定扫描包扫描.class文件,生成Resource对象;第三步、解析.class文件并注解归类,生成MetadataReader对象;第四步、使用第一步的注解过滤器过滤出有@Component类;第五步、生成BeanDefinition对象;第六步、把BeanDefinition注册到Spring容器。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring 中,使用 MyBatis 进行数据库操作时,可以使用 `@Mapper` 注解标注 Mapper 接口,让 Spring 自动扫描并生成该接口的实现类,从而方便进行数据库操作。 具体来说,`@Mapper` 注解是一个组合注解,包含了 `@Component` 和 `@MapperScan` 两个注解的功能。其中,`@Component` 注解将 Mapper 接口标记为一个 Spring 组件,使其能够被 Spring 扫描并加入到 IoC 容器中;`@MapperScan` 注解用于指定扫描 Mapper 接口的包路径。 `@Mapper` 注解的实现原理主要依赖于 SpringBeanPostProcessor 接口和 JDK 的动态代理技术。当 Spring 扫描到一个被 `@Mapper` 注解标注的 Mapper 接口时,会生成该接口的代理类并加入到 IoC 容器中。该代理类会拦截接口中的方法调用,并将其转发给 MyBatis 的 SqlSession 完成具体的数据库操作。 下面是 `@Mapper` 注解的部分源码: ```java @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Component public @interface Mapper { /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) */ String value() default ""; } ``` 可以看到,`@Mapper` 注解中除了包含 `@Component` 注解的功能外,还定义了一个 `value()` 方法,用于指定该组件在 Spring IoC 容器中的名称。 需要注意的是,使用 `@Mapper` 注解自动扫描 Mapper 接口的功能需要在 Spring 的配置文件中添加 `@MapperScan` 注解。例如: ```java @Configuration @MapperScan("com.example.mapper") public class MybatisConfig { // ... } ``` 其中,`@MapperScan` 注解的参数为 Mapper 接口所在的包路径。这样,Spring 在启动时会自动扫描该包下的 Mapper 接口,并生成其实现类并加入到 IoC 容器中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值