前言
前面我在这篇博文:【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser) 解释Spring解析@Configuration的时候,提到过了解析:@PropertySource、@ComponentScan、@Import…等等的解析过程。
它在这个类里大概如下:ConfigurationClassParser#doProcessConfigurationClass:
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass){ //1、Process any @PropertySource annotations //2、Process any @ComponentScan annotations === ==== 这一步解析,现在就是我们今天的主题,下面会对源码进行分析 ==== //3、Process any @Import annotations //4、Process any @ImportResource annotations //5、Process individual @Bean methods //6、Process default methods on interfaces //7、Process superclass, if any }
因为扫描模式在应用(我们自己在涉及自己的框架的时候,扫描模式也会被大量的、广泛的应用)使用非常的广泛,因此本文有必要来说说@ComponentScan的原理,旨在掌握它的运行过程,然后学以致用。
Spring Boot默认扫描Bean的处理,就是基于@ComponentScan这个注解的
源码分析
入口处源码
前言部分已经提到了入口处,因此这里直接贴出此部分的源码吧:
// Process any @ComponentScan annotations // 拿到该类上面所有的@ComponentScan注解,包含重复注解 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); // 不为空,且此类不会被跳过,就开始解析吧 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 // 最终委托给了componentScanParser去完成这件事:至于componentScanParser是什么呢?下面有解释 // 备注:这个方法虽然有返回值,但是其实内部都已经把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 //这里面有一个重要的一步,还需要把每个Bean检测一遍。因为Scan出来的Bean,还有可能是@Configuration的,或者标注有@Import等等一些列注解的,因此需要再次交给parse一遍,防止疏漏 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`是什么呢? private final ComponentScanAnnotationParser componentScanParser; // 然后在ConfigurationClassParser的构造函数里,对他进行了初始化 public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader, BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) { this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; this.environment = environment; this.resourceLoader = resourceLoader; this.registry = registry; // 这里对componentScanParser 进行初始化。持有环境、ResourceLoader、名字生成器、注册器的引用 this.componentScanParser = new ComponentScanAnnotationParser(environment, resourceLoader, componentScanBeanNameGenerator, registry); // 初始化condition的计算器,持有注册器、环境、ResourceLoader的引用 this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); }
ComponentScanAnnotationParser:准们解析@ComponentScan的类
从访问权限(Default)来看,它被定义为Spring的一个自己内部使用的工具类。它的惟一一个public方法为:parse()
:
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { // 第一句代码就看出了端倪:原来扫描的工作最终还是委托给了ClassPathBeanDefinitionScanner去做 // 注意:useDefaultFilters这个值特别的重要,能够解释伙伴们为何要配置的原因~~~下面讲解它的时候会有详解 ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); // BeanName的生成器,我们可以单独制定。若不指定(大部分情况下都不指定),那就是默认的AnnotationBeanNameGenerator // 它的处理方式是:类名首字母小写 Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); // 这两个属性和scope代理相关的,这里略过,使用较少 ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } // 控制去扫描哪些.clsss文件的模版。一般不设置 默认值为:**/*.class 全扫嘛 scanner.setResourcePattern(componentScan.getString("resourcePattern")); // includeFilters和excludeFilters算是内部处理最复杂的逻辑了,但还好,对使用者是十分友好的 for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } // Spring4.1后出现的。哪怕我是扫描的Bean,也支持懒加载啦 boolean lazyInit = componentScan.getBoolean("lazyInit"); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true); } // 这里属于核心逻辑,核心逻辑,核心逻辑 Set<String> basePackages = new LinkedHashSet<>(); String[] basePackagesArray = componentScan.getStringArray("basePackages"); // Spring在此处有强大的容错处理。瑞然他是支持数组的,但是它这里也容错处理:支持,;换行等的符号分隔处理 // 并且,并且更强大的地方在于:它支持${...}这种占位符的形式,非常的强大。我们可以动态的进行扫包了~~~~~厉害了我的哥 for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } //basePackageClasses有时候也挺好使的。它可以指定class,然后这个class所在的包(含子包)都会被扫描 for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } // 如果我们没有指定此值,它会取当前配置类所在的包 比如SpringBoot就是这么来干的 if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); // 最后,最后,把@ComponentScan的属性都解析好了,就交给scanner去扫描吧 // 因为都准备好了,所以这里直接调用的doScan()哦~ return scanner.doScan(StringUtils.toStringArray(basePackages)); }
这里再次提示一下:@ComponentScan(basePackages = "${xxx}") 是支持这么写的 可谓非常暖心。
ClassPathBeanDefinitionScanner 类路径下的Bean定义扫描器
参考博文:【小家Spring】Spring容器加载Bean定义信息的两员大将:AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner
其实这个类我们早就接触过了,前面讲到AnnotationConfigApplicationContext容器初始化的时候,就讲到了它,它可以去扫描特定类型的组件。它有它自己的默认扫描策略。它继承自ClassPathScanningCandidateComponentProvider:
下面我们先从它的构造函数说起:
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment, @Nullable ResourceLoader resourceLoader) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; // 我们发现这个useDefaultFilters特别重要,默认情况下他是true,只会扫描默认的注解们 // 至于是哪些注解,看下面 if (useDefaultFilters) { registerDefaultFilters(); } setEnvironment(environment); setResourceLoader(resourceLoader); } //registerDefaultFilters(); protected void registerDefaultFilters() { // @Component显然默认是必须要扫描的嘛 this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); // 下面两个是,默认也支持JSR-250规范的`@ManagedBean`和JSR-330规范的@Named注解(但是我并不建议大家使用,还是使用Spring源生的吧) try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. } try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } }
需要说明的是:@Controller、@ControllerAdvice、@Service、@Repository都属于@Component范畴。当然还有@Configuration它也是