Spring注解扫描原理浅析

Spring注解扫描原理浅析

一、概述

​ 本篇文章将会带着大家通过阅读源码的方式去揭秘Spring的注解扫描原理,如果你想让源码可编辑,那么可以把源码下载下来然后进行调试,关于编译Spring源码,我推荐这篇文章——(spring源码系列(六)——番外篇如何编译spring的源码

​ 很多人一提到源码都会比较排除,可能是因为注释都是英文,又或者类名或方法名太长记不住等原因。本篇文章会手把手带你看源码,中间过程不重要,很多代码不需要过多关注,只需要对重点代码片段和方法进行关注即可。废话不多说,我们这就开始吧!

二、测试代码

public static void main(String[] args) {

	AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
	// 这里可以注册一些配置类
    // applicationContext.register(MyConfig.class);
	applicationContext.refresh();

}

三、源码探究

1、ConfigurationClassPostProcessor 的注册

点进AnnotationConfigApplicationContext的空构造函数,可以看到下面的代码

public AnnotationConfigApplicationContext() {
	// new了一个Reader
	this.reader = new AnnotatedBeanDefinitionReader(this);
	// new了个Scanner
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}

进入new AnnotatedBeanDefinitionReader(this)

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
	this(registry, getOrCreateEnvironment(registry));
}

进入this(registry, getOrCreateEnvironment(registry))

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    Assert.notNull(environment, "Environment must not be null");
    this.registry = registry;
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

进入AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)方法

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
    registerAnnotationConfigProcessors(registry, null);
}

继续往里走,就回到达我们的目的地

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
        BeanDefinitionRegistry registry, @Nullable Object source) {
     // 省略...   
     
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        // 创建ConfigurationClassPostProcessor.class对应的BeanDefinition
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        // 注册BeanDefinition
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
    
	// 省略...
}

正如你所见,在这个方法中,Spring往容器中事先注册了许多的Bean,其中ConfigurationClassPostProcessor这个Bean是和我们注解的扫描息息相关的

2、ConfigurationClassPostProcessor 的功能
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
    PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    // 省略...
}

进入ConfigurationClassPostProcessor这个类可以很清晰的看到,它实现了一个BeanDefinitionRegistryPostProcessor接口,接着我们进入到这个接口中去

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

通过代码可以看到它是接口BeanFactoryPostProcessor的子类

public interface BeanFactoryPostProcessor {

	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

其这个接口就只有一个方法。请记住现在这个方法,一会儿后面会解释为什么这个类那么重要

3、refresh() —— 启动方法

现在,进入到之前测试代码中的refresh()方法中,这个方法是用于启动容器的,现在我们来看一下它的总体流程

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
		
    // 省略...

    try {

        // 调用一系列的BeanFactoryPostProcessor对Bean工厂进行后置处理
        // 实例化并调用所有已注册的 BeanFactoryPostProcessor bean
        // 会先执行所有 BeanDefinitionRegistryPostProcessors 类中的方法,这个是BeanFactoryPostProcessor的子类
        // Invoke factory processors registered as beans in the context.
        invokeBeanFactoryPostProcessors(beanFactory);


        // 省略...
    }

	// 省略...
}

这个方法中,我们只需要关注invokeBeanFactoryPostProcessors这一个方法,接下来我们进入其中一探究竟

/**
 * Instantiate and invoke all registered BeanFactoryPostProcessor beans,
 * respecting explicit order if given.
 * <p>Must be called before singleton instantiation.
 */
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

   	// 省略...
}

通过翻译注释,我们可以知道,这个方法会注册所有实现了BeanFactoryPostProcessor接口的bean,接着继续深入。下面的方法可能会比较长,但是就一个

	public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

		// 保存了已经执行过的BeanDefinitionRegistryPostProcessor
		// 目的是防止重复执行
		// Invoke BeanDefinitionRegistryPostProcessors first, if any.
		Set<String> processedBeans = new HashSet<>();

		// 如果实现了BeanDefinitionRegistry
		if (beanFactory instanceof BeanDefinitionRegistry) {
			BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
			// 存放所有找出来的BeanFactoryPostProcessor(父类)
			List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
			// 找出所有BeanDefinitionRegistryPostProcessor(子类)
			List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

			// 遍历用户通过API往容器添加的BeanFactoryPostProcessor对象
			for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
				if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
					BeanDefinitionRegistryPostProcessor registryProcessor =
							(BeanDefinitionRegistryPostProcessor) postProcessor;
					// 如果是子类就直接执行postProcessBeanDefinitionRegistryf方法
					registryProcessor.postProcessBeanDefinitionRegistry(registry);
					registryProcessors.add(registryProcessor);
				}
				else {
					regularPostProcessors.add(postProcessor);
				}
			}

			// Do not initialize FactoryBeans here: We need to leave all regular beans
			// uninitialized to let the bean factory post-processors apply to them!
			// Separate between BeanDefinitionRegistryPostProcessors that implement
			// PriorityOrdered, Ordered, and the rest.
			List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

			// 通过翻译注释可以知道,此处会调用实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessors,通过前面的代码可以知道,
            // Spring内置的ConfigurationClassPostProcessor是实现了的,并且只有它实现了,所以此处的size = 1
			// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				// 实现联动PriorityOrdered接口最优先执行
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
					// 实例化,并放入单例池中
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			// 对实现了PriorityOrdered的BeanDefinitionRegistryPostProcessors进行排序
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			// 先调用实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
			// 在此处调用了接口中的postProcessBeanDefinitionRegistry方法
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();

			// 这里需要再次去容器中获取是因为在上一步过后,容器中可能注册了新的BeanDefinitionRegistryPostProcessors
             // 现在调用的是实现了Ordered接口的容器中可能注册了新的BeanDefinitionRegistryPostProcessors
			// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				// Ordered仅次于POrdered执行
				if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			// 不难看出执行顺序是PriorityOrdered -> Order -> 普通的,即使前面又注册了新的实现了PriorityOrdered的,在排序后,PriorityOrdered一定会优先执行
			// 每次执行都会优先执行实现了POrder接口的
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			// 再调用所实现了Ordered接口的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
			currentRegistryProcessors.clear();

			// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
			boolean reiterate = true;
			// 因为可能会出现Registry注册Registry这样的套娃情况,所以用了while-true
			while (reiterate) {
				reiterate = false;
				// 如果容器中还有BeanDefinitionRegistryPostProcessor就再循环一边
				postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
				for (String ppName : postProcessorNames) {
					if (!processedBeans.contains(ppName)) {
						currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
						processedBeans.add(ppName);
						reiterate = true;
					}
				}
				sortPostProcessors(currentRegistryProcessors, beanFactory);
				registryProcessors.addAll(currentRegistryProcessors);
				// 最后调用再调用两个几口都没实现的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
				invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
				currentRegistryProcessors.clear();
			}

			// 调用BeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法,因为这个Registry接口继承了BeanFactoryPostProcessor
			// Now, invoke the postProcessBeanFactory callback of all processors handled so far.
			// 先调用子类的
			invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
			// 再调用父类的(API提供的)
			invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
		}

		else {
			// 调用BeanFactoryPostProcessor接口的postProcessBeanFactory(beanFactory)方法
			// Invoke factory processors registered with the context instance.
			invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
		}

		// 执行扫描出来的(父类)
		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let the bean factory post-processors apply to them!
		String[] postProcessorNames =
				beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

		// Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
		// Ordered, and the rest.
		List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		for (String ppName : postProcessorNames) {
			if (processedBeans.contains(ppName)) {
				// skip - already processed in first phase above
			}
			else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				// getBean会进行实例化
				priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
			}
			// 后面只保存了Bean的名字,并没有先实例化,可能是因为BD会被上面实现了PriorityOrdered的BD修改
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
				orderedPostProcessorNames.add(ppName);
			}
			else {
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		// 最先调用实现了PriorityOrdered的BeanFactoryPostProcessors中的postProcessBeanFactory方法
		// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
		invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

		// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
		List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
		for (String postProcessorName : orderedPostProcessorNames) {
			orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		sortPostProcessors(orderedPostProcessors, beanFactory);
		// 再调用实现了Ordered接口的BeanFactoryPostProcessor中的postProcessBeanFactory方法
		invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

		// 最后才调用普通的BeanFactoryPostProcessor的方法
		// Finally, invoke all other BeanFactoryPostProcessors.
		List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
		for (String postProcessorName : nonOrderedPostProcessorNames) {
			nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

		// 因为上面的BeanFactoryPostProcessor都执行完之后可能会对一些BeanDefinition进行了修改操作,因此讲MergedBD都标记为已已过期的
		// Clear cached merged bean definitions since the post-processors might have
		// modified the original metadata, e.g. replacing placeholders in values...
		beanFactory.clearMetadataCache();
	}

**小结:**总结一下这个方法,首先会根据PriorityOrdered -> Ordered-> 没实现这两个接口 这样的顺序去调用所有BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法,最后会调用所有BeanFactoryPostProcessorpostProcessBeanFactory方法。换句话说,上面提到的ConfigurationClassPostProcessor因为实现了这两个接口,所以这两个方法都会被调用。接下来我们去看看这个接口里面都是一些什么内容吧!

4、ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        // 省略...
        processConfigBeanDefinitions(registry);
    }
}

进入processConfigBeanDefinitions方法

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {

	// 省略...
	do {
        // 通过pase方法解析配置类,其实也就是在这个方法中去做的Spring的扫描
        parser.parse(candidates);
        parser.validate();

        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        // Read the model and create bean definitions based on its content
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);

        candidates.clear();
        if (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());
}

接着进入parse方法

public void parse(Set<BeanDefinitionHolder> configCandidates) {
   // 省略...
   // 解析通过注解得到的Bean
   parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
   // 省略...    
}

进入到解析AnnotatedBeanDefinition的parse方法中,然后再进入processConfigurationClass方法

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }

    // 用ConfigurationClass保存配置类种的信息
    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, filter);
    do {
        // doProcessConfigurationClass中会解析配置类上的注解
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    }
    while (sourceClass != null);

    this.configurationClasses.put(configClass, configClass);
}

接着进入到进入到doProcessConfigurationClass中就会看到核心代码了

protected final SourceClass doProcessConfigurationClass(
        ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
        throws IOException {

    // 如果加了@Component注解就会首先递归地处理内部类
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // Recursively process any member (nested) classes first
        processMemberClasses(configClass, sourceClass, filter);
    }

    // 解析@PropertySource注解
    // Process any @PropertySource annotations
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), PropertySources.class,
            org.springframework.context.annotation.PropertySource.class)) {
        if (this.environment instanceof ConfigurableEnvironment) {
            // 处理注解中的内容,并抽象成PropertySource对象
            processPropertySource(propertySource);
        }
        else {
            logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                    "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }

    // 开始解析@ComponentScans和@ComponentScan注解
    // Process any @ComponentScan annotations
    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
            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
            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());
                }
            }
        }
    }

    // 解析@Import注解
    // Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    // 解析@ImportResource注解
    // Process any @ImportResource annotations
    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);
        }
    }

    // 解析加了@Bean的方法
    // Process individual @Bean methods
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // 处理接口
    // Process default methods on interfaces
    processInterfaces(configClass, sourceClass);

    // 处理父类
    // Process superclass, if any
    if (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 recurse
            return sourceClass.getSuperClass();
        }
    }

    // No superclass -> processing is complete
    return null;
}

从上面的代码中不难看出,注解解析的顺序是 @Component —> @PropertySource —> @ComponentScans和@ComponentScan —> @Import —> @ImportResource —> @Bean —> 处理接口上的注解 —> 处理父类的注解,对于一些可能引入新的Bean的注解,内部还会调用上面提到过的processConfigBeanDefinitions方法,然后又会进入到这里来,很明显,这是一个递归的过程,因为配置类可能会引入新的配置类。

四、小结

Spring容器通过refresh()方法启动,在启动过程中,会最先执行实现了BeanDefinitionRegistryPostProcessor接口和PriorityOrdered接口的类的postProcessBeanDefinitionRegistry方法,而满足上面这个条件的就只有ConfigurationClassPostProcessor这一个类。

他会去解析所有的配置类,并且解析配置类的方法是递归调用的。因源码过于庞大,所有本篇文章只是带大家简单看了一下Spring注解扫描的原理,对某个注解的解析原理大家可以根据自己想法去方法内部查看。

如果以上内容有问题,欢迎评论指出

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生命中有太多不确定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值