其他分享
首页 > 其他分享> > ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner

作者:互联网

该类用来扫描 classpath(类路径)上的类,并注册为 BeanDefinition。默认会扫描 Spring 中的 @Component、@Repository、@Service 或 @Controlle 注释的类;还有 Java EE 6's javax.annotation.ManagedBean 和 JSR-330's javax.inject.Named 注解的类。

因为 @Repository、@Service 和 @Controlle 注解都被 @Component 注解了,所以能被扫描到。

该类没有无参构造方法,最少传入一个 BeanDefinitionRegistry 实例,并且所有的构造方法最终都会调用:

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
         Environment environment, @Nullable ResourceLoader resourceLoader) {

    this.registry = registry;
    // useDefaultFilters 默认为 true;
    if (useDefaultFilters) {
        // 初始化包含过滤集合,这个集合就是一个白名单;在扫描 classpath 下所有类的时候,某个类只有满足白名单才会注册为 BeanDefinition。
        // private final List<TypeFilter> includeFilters = new ArrayList<>();
        // 会向集合中添加 Component、ManagedBean 和 Named 注解类型的包含过滤。
        registerDefaultFilters();
    }
    // 就是将 environment 实例设置到当前扫描器
    setEnvironment(environment);

    // 该方法最主要的一件事情就是读取 META-INF/spring.components 文件,并封装成 CandidateComponentsIndex 实例。
    setResourceLoader(resourceLoader);
}

doScan

此方法是这个类中最主要的方法,用来扫描类路径并将满足条件的类注册为 BeanDefinition

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
        // findCandidateComponents 方法具体看下面。
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                // 1.将 BeanDefinitionDefaults 的值设置到 BeanDefinition,这是当作默认值使用。
                // 2.autowireCandidatePatterns 做什么?
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                // 将 Lazy、Primary、DependsOn、Role、Description 注解的值设到 BeanDefinition 中。
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder =
                    AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                // 向容器中注册 BeanDefinition 和 别名。
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

findCandidateComponents

这个方法就是用来扫描出满足条件的类,并封装为 BeanDefinition。

解释上图中的“判断组件索引是否可用”:

首先 CandidateComponentsIndex 实例必须存在,然后再判断包含过滤集合中的 AnnotationTypeFilter(注解) 和 AssignableTypeFilter(指定类) 过滤器是不是都使用了 @Indexed 注解,都使用了才会启用组件索引;如果包含过滤集合中存在其它类型过滤器则直接禁用组件索引。

值得注意的是:

判断包含过滤集合中的注解或指定类上有没有使用 @Indexed 注解;例如 @XXX 使用了 @Component 元注解,虽然 @Component 使用 @Indexed,但是判断是不会成立的,因为 @XXX 没有直接使用 @Indexed 元注解。

这样做是为了保证,自定义注解或自定义类型的 Bean 能被扫描到,因为编译的时候只会将 @Indexed 注解和元注解的类保存到文件中,所以如果当前容器的包含过滤集合中有其它的过滤类型,就会导致禁用组件索引。

ScopedProxyMode

MySingletonBean 依赖 MyPrototypeBean,你想要每次调用 getPrototypeBean 时都返回一个新的实例,这个时候就需要配置 ScopedProxyMode。

ScopedProxyMode 默认值为 No,它还有两个选项 INTERFACES 和 TARGET_CLASS;如果 MyPrototypeBean 实现了接口那么可以指定为 INTERFACES,它是使用 JDK 的动态代理来创建 MyPrototypeBean 的代理对象,否则就使用 TARGET_CLASS,它是使用 CGLIB 来创建 MyPrototypeBean 的代理对象。

也就是说 MySingletonBean 类中的 prototypeBean 属性被赋值为代理对象,当调用代理对象的方法时,会委托给真实对象。

注意:除了原型作用域,Request 或 Session 等作用域一样可以配置 ScopedProxyMode。

import java.time.LocalDateTime;
public class MyPrototypeBean {
  private String dateTimeString = LocalDateTime.now().toString();
  public String getDateTime() { return dateTimeString; }
}
/////////////////////////////////////////////////////
import org.springframework.beans.factory.annotation.Autowired;
public class MySingletonBean {
  @Autowired
  private MyPrototypeBean prototypeBean;
  public void showMessage() { System.out.println("Hi, the time is "+prototypeBean.getDateTime()); }
}
////////////////////////////////////////////////////
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.*;
@Configuration
public class AppConfig {
  @Bean
  @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
  public MyPrototypeBean prototypeBean () { return new MyPrototypeBean(); }

  @Bean
  public MySingletonBean singletonBean () { return new MySingletonBean(); }

  public static void main (String[] args) throws InterruptedException {
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
      MySingletonBean bean = context.getBean(MySingletonBean.class);
      bean.showMessage();
      Thread.sleep(1000);
      bean.showMessage();
  }
}

注意:在 Spring 的启动流程中,注册 BeanDefinition 之前会设置 ScopedProxyMode。

标签:MyPrototypeBean,candidate,MySingletonBean,ClassPathBeanDefinitionScanner,注解,publ
来源: https://www.cnblogs.com/superylg/p/16381917.html