SpringBoot 源码解析5:ConfigurationClassPostProcessor整体流程和@ComponentScan源码分析

SpringBoot 源码解析5:ConfigurationClassPostProcessor整体流程和@ComponentScan源码分析

    • 1. 知道以下几点,读ConfigurationClassPostProcessor源码会更轻松
    • 2. 源码解析 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
      • 2.1 ConfigurationClassPostProcessor#processConfigBeanDefinitions
        • 2.1.1 ConfigurationClassUtils#checkConfigurationClassCandidate
        • 2.1.2 ConfigurationClassUtils#isConfigurationCandidate
      • 2.2 ConfigurationClassParser#parse
        • 2.2.1 ConfigurationClassParser#processConfigurationClass
        • 2.2.2 ConfigurationClassParser#doProcessConfigurationClass
    • 3. @ComponentScan 源码分析
      • 3.1 ComponentScanAnnotationParser#parse
      • 3.2 ClassPathBeanDefinitionScanner#doScan
      • 3.3 ClassPathScanningCandidateComponentProvider#scanCandidateComponents
      • 3.4 ClassPathScanningCandidateComponentProvider#isCandidateComponent
      • 3.5 @ComponentScan源码总结
    • 4. TODO 其他注解

1. 知道以下几点,读ConfigurationClassPostProcessor源码会更轻松

  1. 配置类后置处理器ConfigurationClassPostProcessor,是对Spring注解式配置支持的核心,负责对@Component、@ComponentScan、@Import等注解的解析,将BeanDefinition注册到beanFactory。
  2. ConfigurationClassPostProcessor是在AnnotationConfigServletWebServerApplicationContext创建的时候注册的。参考 SpringBoot 源码解析2:启动流程
  3. BeanDefinitionRegistryPostProcessor实现了BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor。ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry在刷新容器的时候AbstractApplicationContext#invokeBeanFactoryPostProcessors方法中调用。参考 SpringBoot 源码解析4:refresh 方法解析

2. 源码解析 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {int registryId = System.identityHashCode(registry);if (this.registriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);}if (this.factoriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);}this.registriesPostProcessed.add(registryId);processConfigBeanDefinitions(registry);
}

registry其实就是就是beanFactory,一个beanFactory只会处理一次

2.1 ConfigurationClassPostProcessor#processConfigBeanDefinitions

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {//存放有@Configuration的BeanDefinitionHolderList<BeanDefinitionHolder> configCandidates = new ArrayList<>();//获取到手动注册的BeanDefinition,在此之前已经将启动类注册了,请参考springBoot启动流程String[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);//判断BeanDefinition是否存在ConfigurationClassPostProcessor.configurationClass属性if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}// 不存在ConfigurationClassPostProcessor.configurationClass,就会配置此属性。// 配置里@Configuration就会返回true,才会放入到候选配置里面else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were found// 没有发现@Configuration,就会立马返回if (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicable// 按照@Order排序configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application context// 探测有没有自定义的BeanName生成器,如果有的话就使用自定义的beanName生成器SingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}if (this.environment == null) {this.environment = new StandardEnvironment();}// Parse each @Configuration class// 创建解析器,解析每一个有@Configuration的类ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());do {// 解析有@Configuration的BeanDefinitionparser.parse(candidates);// 对Configuration和Bean注解进行了校验parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// Read the model and create bean definitions based on its contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 此时有一些BeanDefinition没有被注册,比如@Bean、@Import中的ImportBeanDefinitionRegistrarthis.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);candidates.clear();// 这是在一个循环里面,判断bean工厂在解析前和解析后是否有新注册的BeanDefinition。// 如果有的话,那么这些BeanDefinition也有可能是配置类,那么就会去解析这些BeanDefinitionif (registry.getBeanDefinitionCount() > candidateNames.length) {String[] newCandidateNames = registry.getBeanDefinitionNames();Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}candidateNames = newCandidateNames;}}while (!candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classesif (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext.((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}
}
  1. 获取到所有注册到的BeanDefinition,通过ConfigurationClassPostProcessor.configurationClass属性,判断BeanDefinition有没有被处理过。下面会讲ConfigurationClassUtils#checkConfigurationClassCandidate。
  2. 如果没有处理,那么就会调用 ConfigurationClassUtils#checkConfigurationClassCandidate判断当前的BeanDefinition是否为配置类。只有当它为配置类,后面的配置类解析器才会对它进行解析。
  3. 兼容了自定义的Bean的名称生成器,可以使用用户自定义的生成器。
  4. 创建配置类解析器ConfigurationClassParser,对已排好序的BeanDefinitionHolder进行解析。
  5. 对Configuration和Bean注解进行一些校验。
  6. 判断bean工厂在解析前和解析后是否有新注册的BeanDefinition。如果有的话,那么这些BeanDefinition也有可能是配置类,那么就会去解析这些BeanDefinition。
2.1.1 ConfigurationClassUtils#checkConfigurationClassCandidate
校验当前的BeanDefinition是否为配置类
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {String className = beanDef.getBeanClassName();if (className == null || beanDef.getFactoryMethodName() != null) {return false;}AnnotationMetadata metadata;if (beanDef instanceof AnnotatedBeanDefinition &&className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {// Can reuse the pre-parsed metadata from the given BeanDefinition...metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();}else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {// Check already loaded Class if present...// since we possibly can't even load the class file for this Class.// 这些类型Spring不可作为配置类Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||BeanPostProcessor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass) ||EventListenerFactory.class.isAssignableFrom(beanClass)) {return false;}metadata = AnnotationMetadata.introspect(beanClass);}else {try {MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);metadata = metadataReader.getAnnotationMetadata();}catch (IOException ex) {if (logger.isDebugEnabled()) {logger.debug("Could not find class file for introspecting configuration annotations: " +className, ex);}return false;}}// 判断获取Configuration注解的属性Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {// Configuration 注解的proxyBeanMethods属性为true,则为BeanDefinition为full,会被代理beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);}// 当前的BeanDefinition"能作为配置类",配置属性为lite,不被代理else if (config != null || isConfigurationCandidate(metadata)) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);}else {// Configuration注解不存在return false;}// It's a full or lite configuration candidate... Let's determine the order value, if any.Integer order = getOrder(metadata);if (order != null) {beanDef.setAttribute(ORDER_ATTRIBUTE, order);}return true;
}
  1. 判断了BeanFactoryPostProcessor、BeanPostProcessor、AopInfrastructureBean、EventListenerFactory类型不能作为配置类。
  2. 如果当前BeanDefintion没有@Configuration,就不能作为配置类。
  3. 如果有@Configuration并且proxyBeanMethods属性为true,能作为配置类,就会配置为full。为full的会被代理。
  4. 如果有@Configuration并且proxyBeanMethods属性为false,并且判断 “能作为配置类”,就会配置为lite。lite不会被代理。
  5. 这里简单提一下@Configuration代理过程,如果Configuration的proxyBeanMethods属性为true,那么当前的BeanDefinition为full,就会在ConfigurationClassPostProcessor#enhanceConfigurationClasses会创建代理类,从而创建代理对象。代理对象在调用beanMethod(@Bean方法)时,会被BeanMethodInterceptor拦截器拦截。代理拦截器的逻辑是这样的:在第一次调用beanMethod完毕后生成bean之后,会根据类名和方法名称生成一个缓存,第二次生成调用同一个beanMethod,会直接从缓存中取,而不是调用beanMethod去创建对象。
  6. 那么,什么样"能作为配置类"呢?
2.1.2 ConfigurationClassUtils#isConfigurationCandidate
private static final Set<String> candidateIndicators = new HashSet<>(8);static {candidateIndicators.add(Component.class.getName());candidateIndicators.add(ComponentScan.class.getName());candidateIndicators.add(Import.class.getName());candidateIndicators.add(ImportResource.class.getName());
}public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {// Do not consider an interface or an annotation...if (metadata.isInterface()) {return false;}// Any of the typical annotations found?for (String indicator : candidateIndicators) {if (metadata.isAnnotated(indicator)) {return true;}}// Finally, let's look for @Bean methods...try {return metadata.hasAnnotatedMethods(Bean.class.getName());}catch (Throwable ex) {if (logger.isDebugEnabled()) {logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);}return false;}
}

能作为配置类的条件:首先,接口肯定是不能作为配置类的。类上有Component、ComponentScan、Import、ImportResource,或者方法中有@Bean注解的,都能作为配置类。

2.2 ConfigurationClassParser#parse

public void parse(Set<BeanDefinitionHolder> configCandidates) {for (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd = holder.getBeanDefinition();try {if (bd instanceof AnnotatedBeanDefinition) {parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {parse(bd.getBeanClassName(), holder.getBeanName());}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);}}this.deferredImportSelectorHandler.process();
}

在解析完配置类之后,才会对deferredImportSelector进行处理。

2.2.1 ConfigurationClassParser#processConfigurationClass
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {processConfigurationClass(new ConfigurationClass(metadata, beanName));
}protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {// 支持Conditional注解if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {return;}ConfigurationClass existingClass = this.configurationClasses.get(configClass);if (existingClass != null) {if (configClass.isImported()) {if (existingClass.isImported()) {existingClass.mergeImportedBy(configClass);}// Otherwise ignore new imported config class; existing non-imported class overrides it.return;}else {// Explicit bean definition found, probably replacing an import.// Let's remove the old one and go with the new one.this.configurationClasses.remove(configClass);this.knownSuperclasses.values().removeIf(configClass::equals);}}// Recursively process the configuration class and its superclass hierarchy.SourceClass sourceClass = asSourceClass(configClass);do {sourceClass = doProcessConfigurationClass(configClass, sourceClass);}while (sourceClass != null);this.configurationClasses.put(configClass, configClass);
}
  1. configurationClasses中存放着已解析的配置信息,如果没有加载,则调用ConfigurationClassParser#doProcessConfigurationClass方法解析配置类,解析完毕之后放入到configurationClasses。
  2. 如果已解析并且importedBy不为空,则合并importedBy。如果不是Import,则清除缓存,重新解析。
2.2.2 ConfigurationClassParser#doProcessConfigurationClass
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)throws IOException {// 处理@Component注解,处理当前类声明的类DeclaredClasse,因为当前类的内部类也有可能是配置类if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes firstprocessMemberClasses(configClass, sourceClass);}// Process any @PropertySource annotations// 处理@PropertySource 注解for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}// 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 immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (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());}}}}// Process any @Import annotations// 处理@Import注解processImports(configClass, sourceClass, getImports(sourceClass), true);// Process any @ImportResource annotations// 处理@ImportResource注解AnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource != null) {String[] resources = importResource.getStringArray("locations");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// Process individual @Bean methods// 处理@Bean注解Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfacesprocessInterfaces(configClass, sourceClass);// Process superclass, if anyif (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn null;
}
  1. 此方法返回的是父类的资源,如果没有父类,说明类当前类已解析完毕。
  2. 对Component、PropertySource、ComponentScan、Import 、ImportResource、Bean等注解进行了解析。
  3. 我们最关心的是@ComponentScan和@Component,下面我们就对它们进行源码分析。

3. @ComponentScan 源码分析

在创建ConfigurationClassParser的时候,就创建了componentScanParser。
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;this.componentScanParser = new ComponentScanAnnotationParser(environment, resourceLoader, componentScanBeanNameGenerator, registry);this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
}

3.1 ComponentScanAnnotationParser#parse

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {// 创建扫描器,默认配置了三个includeFilters。Component、ManagedBean、Named。ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :BeanUtils.instantiateClass(generatorClass));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));}scanner.setResourcePattern(componentScan.getString("resourcePattern"));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);}}boolean lazyInit = componentScan.getBoolean("lazyInit");if (lazyInit) {scanner.getBeanDefinitionDefaults().setLazyInit(true);}Set<String> basePackages = new LinkedHashSet<>();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);}for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}//如果componentScan没有配置basePackages和basePackageClasses属性,那么就取声明@ComponentScan的类所对应的包名if (basePackages.isEmpty()) {basePackages.add(ClassUtils.getPackageName(declaringClass));}scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {@Overrideprotected boolean matchClassName(String className) {return declaringClass.equals(className);}});// 扫描包return scanner.doScan(StringUtils.toStringArray(basePackages));
}
  1. 此方法只是对扫描器进行了配置,扫描前的准备工作。
  2. ComponentScan扫描的时候有两种过滤器,excludeFilters比includeFilters优先级高:
    2.1. includeFilters:includeFilters匹配到的注册到bean工厂。
    2.2. excludeFilters:excludeFilters匹配到的不会注册到bean工厂。
  3. ClassPathBeanDefinitionScanner如果使用默认的过滤器,就会添加三个includeFilter:Component、ManagedBean、Named。
protected void registerDefaultFilters() {this.includeFilters.add(new AnnotationTypeFilter(Component.class));ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));logger.trace("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.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.}
}
  1. 如果@componentScan没有配置basePackages和basePackageClasses属性,那么就取声明@ComponentScan的class所对应的包名。

3.2 ClassPathBeanDefinitionScanner#doScan

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) {//获取到basePackage下面所有的beanDefinitionSet<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {//解析Scope注解,默认singleton不代理ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());//通过bean的名称生成器生成beanName,默认的是AnnotationBeanNameGenerator。解析Component、ManagedBean、Named的value属性。如果以上注解属性,则AnnotationBeanNameGenerator#buildDefaultBeanName创建默认的beanName,如果类名的前两个字母是大写,则是类的名称,否则就将首字母变成小写。String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {//处理beanDefinition,对BeanDefinition设置了默认的属性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);//Scope代理处理,如果需要代理,则已经注册了原始的BeanDefinition,返回的是代理的BeanDifinition。definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//注册BeanDefinitionregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;
}
  1. 获取到basePackage下面所有的资源,并且解析成BeanDefinition。
  2. 对@Scope注解进行解析,判断是否需要代理,默认singleton不代理。
  3. 通过bean的名称生成器生成beanName,默认的是AnnotationBeanNameGenerator。解析Component、ManagedBean、Named的value属性。如果以上注解属性,则AnnotationBeanNameGenerator#buildDefaultBeanName创建默认的beanName,如果类名的前两个字母是大写,则是类的名称,否则就将首字母变成小写。
  4. 处理beanDefinition,对BeanDefinition设置了默认的属性。
  5. 解析公共的注解Lazy、Primary、DependsOn、Role、Description,对BeanDefinition的属性进行修改。
  6. Scope代理处理,如果需要代理,则已经注册了原始的BeanDefinition,返回的是代理的BeanDifinition。
  7. 注册BeanDefinition。

3.3 ClassPathScanningCandidateComponentProvider#scanCandidateComponents

如何扫描资源,并且生成BeanDefinition的?
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {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 metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//通过ComponentScan的过滤器,去扫描当前资源是否匹配。if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);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;
}

1.由上述可知,componentScan解析器配置了三个默认的includeFilter。所以,有Component注解的class会被扫描到,并且生成BeanDefinition。
2. 获取到当前包下面所有的资源,通过includeFilter和excludeFilter判断是否为候选的spring组件。如果是,就会生成BeanDefinition。其中excludeFilter的优先级高。
3. 最终返回所有获选的BeanDefinition。BeanDefinition中存放了元数据读取器MetadataReader,元数据读取器负责读取资源中类的信息,注解、字段、方法等。

3.4 ClassPathScanningCandidateComponentProvider#isCandidateComponent

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;
}

匹配规则如下

  1. 优先匹配excludeFilter,再匹配includeFilter。
  2. 只要有一个excludeFilter匹配到,就不会添加到候选的BeanDefinition中。
  3. includeFilter匹配到了才会添加到候选的BeanDefinition,否则为非候选。

3.5 @ComponentScan源码总结

  1. ClassPathBeanDefinitionScanner对负责扫描classPath下面的资源文件。扫描的规则是通过includeFilters和excludeFilters完成的,其中注册了三个默认的注解过滤器@Component、@ManagedBean、@Named。
  2. 元数据读取器MetadataReader:有了资源文件,那么也需要一个元数据读取器去读取资源中的内容,比如判断是否有@Component注解,这个功能就是由元数据读取器完成的。
  3. 最终将资源文件和可读取资源文件的MetadataReader封装城BeanDefinition,注册到了DefaultListableBeanFactory#beanDefinitionMap。

4. TODO 其他注解

其他注解后面在写,先写完主流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/638885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

100天精通Python(实用脚本篇)——第113天:基于Tesseract-OCR实现OCR图片文字识别实战

文章目录 专栏导读1. OCR技术介绍2. 模块介绍3. 模块安装4. 代码实战4.1 英文图片测试4.2 数字图片测试4.3 中文图片识别书籍分享专栏导读 🔥🔥本文已收录于《100天精通Python从入门到就业》:本专栏专门针对零基础和需要进阶提升的同学所准备的一套完整教学,从0到100的不…

【动态规划】【广度优先搜索】【状态压缩】847 访问所有节点的最短路径

作者推荐 视频算法专题 本文涉及知识点 动态规划汇总 广度优先搜索 状态压缩 LeetCode847 访问所有节点的最短路径 存在一个由 n 个节点组成的无向连通图&#xff0c;图中的节点按从 0 到 n - 1 编号。 给你一个数组 graph 表示这个图。其中&#xff0c;graph[i] 是一个列…

如何用“VMware安装Ubuntu”win11系统?

一、 下载Ubuntu 企业开源和 Linux |Ubuntu的 二、 安装 三、 启动虚拟机 选中Try or Install Ubuntu Server&#xff0c;按回车

数据结构与算法:图

文章目录 图1) 概念有向 vs 无向度权路径环图的连通性 2) 图的表示3) Java 表示4) DFS5) BFS6) 拓扑排序7) 最短路径DijkstraBellman-FordFloyd-Warshall 8) 最小生成树PrimKruskal 图 1) 概念 图是由顶点&#xff08;vertex&#xff09;和边&#xff08;edge&#xff09;组成…

js提取截图中的中文

从图片中提取中文 安装依赖 npm install tesseract.js 编写代码(ocr_example.js) const Tesseract require(tesseract.js); const path require(path); const imagePath path.resolve(__dirname, path/image); Tesseract.recognize(imagePath,chi_sim, { logger: m >…

Mysql学习笔记系列(一)

本次mysql系列不会讲解具体的查询语句&#xff0c;而是放在mysql的一些性能优化和一些特性上&#xff0c;是学习笔记&#xff0c;供大家参考补充。 慢查询 MySQL的慢查询&#xff0c;全名是慢查询日志&#xff0c;是MySQL提供的一种日志记录&#xff0c;用来记录在MySQL中响应…

P8761 [蓝桥杯 2021 国 BC] 大写

[蓝桥杯 2021 国 BC] 大写 题目描述 给定一个只包含大写字母和小写字母的字符串&#xff0c;请将其中所有的小写字母转换成大写字母后将字符串输出。 输入格式 输入一行包含一个字符串。 输出格式 输出转换成大写后的字符串。 样例 #1 样例输入 #1 LanQiao样例输出 #1…

Meta 标签的力量:如何利用它们提高网站的可见性(上)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

解决 Navicat 在笔记本外接显示器分辨率自适应展示问题

前言 有时候我们使用自己的笔记本电脑会外接一个显示器&#xff0c;但是显示器的分辨率和笔记本又不一样&#xff0c;所以就会导致 Navicat 基于分辨率的问题变得字体很小。具体操作可点击这里&#xff1a; Navicat 分辨率调整

如何在ubuntu22.04安装ROS2

ubuntu22.04安装ROS2 教程 选择对应版本进行安装设置编码添加源安装ROS2设置环境变量 运行ROS2 选择对应版本 通过官方网站&#xff0c;查询Ubuntu与ros对应的版本&#xff0c;版本不一致也会出现安装不成功。 https://wiki.ros.org/ROS/Installation 每一个都可以进行点击&a…

判断子序列

给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子序列&#…

解决电脑文件大小写不敏感问题

第一步&#xff1a;以管理员的身份运行 CMD 第二步&#xff1a; 输入下面命令 fsutil file setCaseSensitiveInfo 路径 enable 路径改成目标文件夹的路径&#xff0c;比如说我也下面 Less-24 这个文件夹里面的文件全部都大小写敏感 这样就 OK 了&#xff0c;注意路径最后要加…

python中三种常用格式化字符串的方法(%s, format,f-string)

前言 python中对字符串格式化是最常见的操作&#xff0c;对字符串格式化一般有三种方法&#xff0c;即%s,format和f-string。因人而异&#xff0c;每个人使用的格式化方法不同&#xff0c;为了在不同场景更高效的使用格式化方法&#xff0c;以及阅读别人的代码&#xff0c;通常…

GitFlow工作流

基于 Git 这一版本控制系统&#xff0c;通过定义不同的分支&#xff0c;探索合适的工作流程来完成开发、测试、修改等方面的需求。 例如&#xff1a;在开发阶段&#xff0c;创建 feature 分支&#xff0c;完成需求后&#xff0c;将此分支合并到 develop 分支上&#xff1b;在发…

深度学习常用代码总结(k-means, NMS)

目录 一、k-means 算法 二、NMS 一、k-means 算法 k-means 是一种无监督聚类算法&#xff0c;常用的聚类算法还有 DBSCAN。k-means 由于其原理简单&#xff0c;可解释强&#xff0c;实现方便&#xff0c;收敛速度快&#xff0c;在数据挖掘、数据分析、异常检测、模式识别、金…

Spring最常用组件注册注解开发案例

Spring常用组件注册注解开发案例 文章目录 Spring常用组件注册注解开发案例1. 组件注册注解1. Configuration2.Bean注解3. Configuration与Bean注解使用案例4. ComponentScan注解5. 自定义TypeFilter指定过滤规则 什么是spring注解开发&#xff1f; 就是不再使用Spring的bean.x…

PHP+vue+Mysql家庭理财管理系统演5x6nf

本文着重阐述了收支管理系统的分析、设计与实现&#xff0c;首先介绍开发系统和环境配置、数据库的设计&#xff0c;对系统的功能需求作出分析&#xff0c;根据需求对系统进行设计&#xff0c;明确各个部分的规范&#xff0c;来完成系统的设计。最后在对设计的系统进行一系列的…

k8s1.27.2版本二进制高可用集群部署

文章目录 环境软件版本服务器系统初始化设置关于etcd签名证书etcd集群部署负载均衡器组件安装设置关于k8s自签证书自签CAkube-apiserver 自签证书kube-controller-manager自签证书kube-scheduler自签证书kube-proxy 自签证书admin 自签证书 控制平面节点组件部署**部署kube-api…

前端开发领域的细分领域与特点

前端开发领域是一个广泛而多样的领域&#xff0c;包括了许多具体的细分领域。本文将介绍前端领域的细分领域&#xff0c;包括Web前端、移动端、桌面端、游戏端和VR/AR前端等&#xff0c;并分析它们各自的特点&#xff0c;以帮助读者更好地了解前端开发的多样性。 一、引言 前…

【python学习】面向对象编程3

面向对象基础 面向对象编程 面向过程编程&#xff1a;类似于工厂的流水线。 优点&#xff1a;逻辑清晰&#xff1b; 缺点&#xff1a;扩展性差。 面向对象编程&#xff1a;核心是对象二字&#xff0c;对象是属性和方法的集合体&#xff0c;面向对象编程就是一堆对象交互。 优…