当前位置: 首页 > news >正文

幼儿园网站建设总结/青岛网站开发公司

幼儿园网站建设总结,青岛网站开发公司,重庆的企业网站,模板网站怎么做SpringBoot 源码解析5:ConfigurationClassPostProcessor整体流程和ComponentScan源码分析 1. 知道以下几点,读ConfigurationClassPostProcessor源码会更轻松2. 源码解析 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry2.1 Configur…

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,那么这些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有可能是配置类,那么就会通过ConfigurationClassParser去解析这些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 其他注解

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

相关文章:

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

SpringBoot 源码解析5&#xff1a;ConfigurationClassPostProcessor整体流程和ComponentScan源码分析 1. 知道以下几点&#xff0c;读ConfigurationClassPostProcessor源码会更轻松2. 源码解析 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry2.1 Configur…...

一.初识Linux 1-3操作系统概述Linux初识虚拟机介绍

目录 一.初识Linux 1.操作系统概述 计算机组成 硬件&#xff1a; 软件&#xff1a; 操作系统&#xff1a; 操作系统工作流程 操作系统作用 常见的操作系统 PC端&#xff1a; 移动端&#xff1a;&#xff08;掌上操作系统&#xff09; 一.初识Linux 2.Linux初识 linu…...

Eureka整合seata分布式事务

文章目录 一、分布式事务存在的问题二、分布式事务理论三、认识SeataSeata分布式事务解决方案1、XA模式2、AT模式3、SAGA模式4.SAGA模式优缺点&#xff1a;5.四种模式对比 四、微服务整合Seata AT案例Seata配置微服务整合2.1、父工程项目创建引入依赖 2.2、Eureka集群搭建2.3、…...

华为云磁盘性能指标(参考)

MD[华为云磁盘性能指标(参考)] 云硬盘&#xff08;Elastic Volume Service, EVS&#xff09; 根据性能&#xff0c;磁盘可分为极速型SSD V2、极速型SSD、通用型SSD V2、超高IO、通用型SSD、高IO、普通IO。 性能指标(参考)&#xff0c;测速说明&#xff1a;操作系统-windows …...

利用OpenGL图形库实现人物动画移动效果

使用OpenGL库实现人物动画移动效果需要涉及到更复杂的图形编程和事件处理。以下是一个简单的例子&#xff0c;使用OpenGL和GLUT库实现人物的基本动画移动效果。 确保你已经安装了OpenGL和GLUT。你可以使用包管理器或者从官方网站下载并安装。 一、如果你已经安装过了OpenGL和…...

History命令解释,及一个相关的bash脚本(如何编写脚本程序从记录文件中提取history命令)

目 录 一、history命令介绍 1、history命令是什么&#xff1f; 2、history的主要功能 二、history命令的用法 1、语法 2、选项说明 3、命令实例 三、history和历史记录文件bash_history 四、history命令的相关配置 1&#xff0c;命令带时间展示-HISTTI…...

apisix 单机部署 linux

安装etcd&#xff1a; cd /home/app rz tar -zxvf etcd-v3.5.4-linux-amd64.tar.gz cd etcd-v3.5.4-linux-amd64 vim start.sh内容&#xff1a; #!/bin/sh nohup etcd --name infra0 --initial-advertise-peer-urls http://127.0.0.1:2380 \--listen-peer-urls http://127.0.…...

Redis 面试题 | 06.精选Redis高频面试题

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

2008年苏州大学837复试机试C/C++

2008年苏州大学复试机试 题目 编写程序充成以下功能: 一、从键盘上输入随机变量x的 10个取样点。X0&#xff0c;X1—X9 的值; 1、计算样本平均值 2、判定x是否为等差数列 3、用以下公式计算z的值(t0.63) 注。请对程序中必要地方进行注释 补充&#xff1a;个人觉得这个题目回忆…...

MySQL笔记-information_schema库中COLUMNS表的一些笔记

mysql建表中可以添加comment&#xff0c;也就是注释&#xff0c;这些注释会写到information_schema库的COLUMNS表中&#xff0c;可以使用如下SQL语句进行查询&#xff1a; SELECT COLUMN_NAME, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA your_data…...

归并排序模板

模板在文末&#xff0c;以下步骤方便理解记忆。 先贴一张快速排序模板步骤&#xff0c;用于对比记忆 归并排序步骤&#xff1a; &#xff08;0&#xff09;如果数组左边界L ≥ 数组右边界&#xff0c;则不需要排序&#xff0c;直接return。 &#xff08;1&#xff09;直接取…...

【NVIDIA】Jetson Orin Nano系列:安装 Qt6、firefox、jtop、flameshot

1、使用命令安装 sudo apt install qtcreator sudo apt install qt6-* sudo apt install libqt6* sudo apt install qml-qt6 sudo apt install qmlscene-qt6 sudo apt install assistant-qt6 sudo apt install designer-qt62、启动 qtcreator 3、常用工具安装 sudo apt in…...

Fastapi+Jsonp实现前后端跨域请求

文章目录 一、实现方法1.后端部分【Fastapi】2.前端部分【JS】二、测试一、实现方法 1.后端部分【Fastapi】 # coding:utf-8import json from fastapi import FastAPI, Response from fastapi.middleware.cors import CORSMiddlewareapp = FastAPI(...

MacOS受欢迎的数据库开发工具 Navicat Premium 15 中文版

Navicat Premium 15 Mac是一款数据库管理工具&#xff0c;提供了一个全面的解决方案&#xff0c;用于连接、管理和维护各种数据库系统。以下是Navicat Premium 15 Mac的一些主要功能和特点&#xff1a; 软件下载&#xff1a;Navicat Premium 15 中文版下载 多平台支持&#xff…...

helm---自动化一键部署

什么是helm?? 在没有这个helm之前&#xff0c;deployment service ingress helm的作用就是通过打包的方式&#xff0c;把deployment service ingress 这些打包在一块&#xff0c;一键式部署服务&#xff0c;类似于yum 官方提供的一个类似于安装仓库的功能&#xff0c;可以实…...

求助帖(setiosflags)的左右对齐问题:

以后自己要注意&#xff0c;如果两个相互矛盾的标志同时被设置&#xff0c;如先设置 setiosflags(ios::right)&#xff0c;然后又设置 setiosflags(ios::left)&#xff0c;那么结果可能就是两个标志都不起作用。因此&#xff0c;在设置了某标志&#xff0c;又要设置其他与之矛盾…...

升级8.0:民生手机银行的“内容解法”

数字化浪潮&#xff0c;滚滚来袭。 随着数字中国建设的持续推进&#xff0c;数字经济正在蓬勃发展。中商产业研究院分析师预测&#xff0c;2023年中国数字经济市场规模将增长至56.7万亿元&#xff0c;占GDP的比重将达到43.5%。 在此浪潮下&#xff0c;数字化的触角蔓延到各行…...

Kubernetes多租户实践

由于namespace本身的限制&#xff0c;Kubernetes对多租户的支持面临很多困难&#xff0c;本文梳理了K8S多租户支持的难点以及可能的解决方案。原文: Multi-tenancy in Kubernetes 是否应该让多个团队使用同一个Kubernetes集群? 是否能让不受信任的用户安全的运行不受信任的工作…...

【GEE】GEE反演地表温度相关问题说明(空洞、Landsat9数据集等)

之前分享了基于GEE-Landsat8数据集地表温度反演&#xff08;LST热度计算&#xff09;&#xff0c;最近有很多小伙伴私信我很多问题&#xff0c;一一回复太慢了&#xff0c;所以今天写篇文章统一回答一下大家的问题。 问题1&#xff1a;数据有很多空洞、某些条带没有数据等 问题…...

【蓝桥备赛】数组分割——组合数学?

题目链接 数组分割 个人思路 两个数组都需要和为偶数&#xff0c;那么就去思考一个数组如何才能和是偶数呢&#xff1f;&#xff1f; 数组里肯定要么是奇数要么是偶数&#xff0c;偶数无论有多少个&#xff0c;都不会改变一个数组的奇偶性。但是奇数个奇数的和还是奇数&…...

iphone5s基带部分电源部分主主电源供电及

时序: 1.,基带电源的供电&#xff0c;基带电源也叫pmu。 首先时序图说电池提供供电&#xff0c;电池是J6接口&#xff0c;视频习惯把接口称之为座子。查U2_RF芯片&#xff0c;发现供电信号为PP_BATT_VCC_CONN&#xff0c;但是没查到跟电池座子有关系&#xff0c;电池座子写的是…...

【每日一题】按分隔符拆分字符串

文章目录 Tag题目来源解题思路方法一&#xff1a;遍历方法二&#xff1a;getline 写在最后 Tag 【遍历】【getline】【字符串】【2024-01-20】 题目来源 2788. 按分隔符拆分字符串 解题思路 方法一&#xff1a;遍历 思路 分隔符在字符串开始和结束位置时不需要处理。 分隔…...

spawn_group_template | spawn_group | linked_respawn

字段介绍 spawn_group | spawn_group_template 用来记录与脚本事件或boss战斗有关的 creatures | gameobjects 的刷新数据linked_respawn 用来将 creatures | gameobjects 和 boss 联系起来&#xff0c;这样如果你杀死boss&#xff0c; creatures | gameobjects 在副本重置之前…...

软考系分之计算机网络规划设计、综合布线、RAID和网络存储等

文章目录 1、概要2、网络的三层模型3、综合布线系统4、廉价磁盘冗余阵列&#xff08;RAID&#xff09;5、网络存储6、总结 1、概要 本篇重点介绍计算机网络中的网络规划设计、综合布线、RAID和网络存储。 2、网络的三层模型 三层模型分为核心层、汇聚层和接入层&#xff0c;接…...

使用ElEment组件实现vue表单校验空值

1.绑定表单组件数组rules 2.在data域中设定组件rules 3.设定调用方法函数 提交校验 取消&#xff1a; 测试页面 提交空值 失去焦点 取消重置 提交后重置...

processing集训day01

介绍 Processing是一门开源编程语言&#xff0c;提供了对图片&#xff0c;动画和声音进行编程的环境。学生&#xff0c;艺术家&#xff0c;设计师&#xff0c;建筑师&#xff0c;研究人员和业余爱好者可以使用Processing进行学习&#xff0c;制作原型以及作为生产工具。你可以…...

java面试——juc篇

目录 一、线程基础 1、进程与线程的区别&#xff1f;&#xff08;⭐⭐⭐&#xff09; 2、并行和并发的区别&#xff08;⭐&#xff09; 3、创建线程的方式有哪些&#xff1f;&#xff08;⭐⭐⭐⭐&#xff09; runnable和Callable的区别&#xff1a; 线程中的run()和 star…...

CSS 实现卡片以及鼠标移入特效

CSS 实现卡片以及鼠标移入特效 文章目录 CSS 实现卡片以及鼠标移入特效0、效果预览默认鼠标移入后 1、创建卡片组件2、添加样式3、完整代码 0、效果预览 默认 鼠标移入后 在本篇博客中&#xff0c;我们将探讨如何使用 CSS 来实现卡片组件&#xff0c;并添加鼠标移入特效&#…...

芯课堂 | SWM34S系列驱动TFT-LCD显示模组应用基本注意事项

1、确认硬件的连接、包括电源、地、RGB 数据线、DCLK\DE\HSYNC\VSYNC 等&#xff0c;显示模组有 DISP、RESET、CS、SCL、SDA 等。 2、确认各电压的正常&#xff0c;包括电源&#xff0c;部分有 IOVCC、VGL、VGH、VCOM 等电压 3、如果应用的 TFT-LCD 模组非演示例程中已适配调…...

java8 列表通过 stream流 根据对象属性去重的三种实现方法

java8 列表通过 stream流 根据对象属性去重的三种实现方法 一、简单去重 public class DistinctTest {/*** 没有重写 equals 方法*/SetterGetterToStringAllArgsConstructorNoArgsConstructorpublic static class User {private String name;private Integer age;}/*** lombo…...