【Spring源码系列】Spring注解扫描-@ComponentScan底层原理解读

前言

先不废话了,直接干吧。

一、Spring扫描-@ComponentScan注解介绍

@ComponentScan作用

@ComponentScan注解的作用可以简述为:将项目中所有被@Component注解直接或者间接标记的类---->组装成BeanDefinition---->然后以key=beanName, value=BeanDefinition的形式存储,为后续生成bean对象做准备

提示:被@Component注解标记的类分为两种情况,这两种情况的类都会被Spring成功扫描。

1、该类直接被@Component注解标记
@Component
public class UserService {}

2、该类间接被@Component注解标记(@Configuration组成注解包含@Component注解)
@Configuration
public class ProConfig {}
--
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

上面是说的通过扫描得到BeanDefinition对象,我们还可以通过直接定义BeanDefinition,或解析spring.xml文件的<bean/ >,或者 @Bean注解得到BeanDefinition对象。(后续章节会分析@Bean注解是怎么生成BeanDefinition的)。IOC容器创建BeanDefinition的一些常见方式

@ComponentScan重要参数

@ComponentScan中有多个参数,其中尤为重要的几个参数如下:

  1. value:用来指定basePackage的路径;
  2. excludeFilters:可以把被@Component标识的类排除扫描;
  3. includeFilters:可以把不被@Component标识的类加入到扫描;

示例:

@ComponentScan(value = "com.zhouyu",
		excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = UserService.class)},
		includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = User.class)})

二、Spring扫描-源码分析

声明关键点

在讲源码之前呢,我们可以先增强几个知识点的概念。这些知识点在阅读Spring扫描源码中起着关键作用。

1、@ComponentScan只会扫描被 @Component标识的类。 诸如@Bean、spring.xml文件的< bean/>标签、编程式声明BeanDefinition等,都不会被@ComponentScan扫描;

2、@ComponentScan的excludeFiltersincludeFilters参数影响着类是否成功被扫描。excludeFilters中指定了一个带有@Component注解的类,该类也不会被扫描;includeFilters中指定了一个不带有@Component注解的类,该类也会被扫描;

3、被@ComponentScan扫描注入到IOC容器中的BeanDefinition,其具体实现类是:ScannedGenericBeanDefinition

4、@ComponentScan扫描涉及到的相关注解:@Conditional@Scope和@Lookup@Lazy@Primary@DependsOn@Role@Description

  • @Conditional的作用是:即使该类被@Component修饰,但是Conditional返回false,该类也不会被成功扫描;
  • @Scope的作用是:仅仅作为一个标识,赋值给BeanDefinition的scope属性;
  • @Lookup:抽象类本身不能被扫描进容器,但是被@Lookup注解修饰则可以;
  • @Lazy、@Primary、@DependsOn、@Role:这几个类是一起被处理的,也只是用来给BeanDefinition赋值,分别对应着:setLazyInit(boolean)、setPrimary(boolean)、setDependsOn(value)、setRole(value)、setDescription(value);

5、须知Resource类和MetadataReader类。

  • Resource[]数组对象会存储basePackage包路径下所有的class的文件对象(注意:是所有的class的文件对象都会存储,无论有没有被@Component修饰);
  • MetadataReader类是Spring用来解析类的信息(比如类名、类中的方法、类上的注解、是否是抽象类…,这些都可以称之为类的元数据)的一个工具类。MetadataReader、ClassMetadata、AnnotationMetadata都可以用来解析类信息;

6、@ComponentScan扫描最终生成两个map:Map<String, BeanDefinition> beanDefinitionMapMap<String, String> aliasMap

  • beanDefinitionMap用于存储BeanDefinition,key是beanName,value是BeanDefinition;
  • aliasMap用于存储bean的别名,key是别名,value是beanName。@Component没有设置bean别名功能,@Bean可以设置别名。
@ComponentScan("org.example")
public class AppConfig {

    /**
     * 声明:
     *  如果@Bean没有设置value参数,那么beanName = "createBookService";
     *  如果@Bean设置了多个value参数,那么beanName = array[0]
     */
    @Bean({"bookService","bookService2"})
    public BookService createBookService(){
        return new BookService();
    }
}	

7、其他:

  • ASM技术:为什么要使用ASM技术,Spring启动的时候需要去扫描,如果指定的包路径比较宽泛,那么扫描的类是非常多的,那如果在Spring启动时就把这些类全部加载进JVM了,这样不太好,所以使用了ASM技术;

源代码解读

源码具体位置:org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
			Assert.notEmpty(basePackages, "At least one base package must be specified");
			Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
			// 遍历basePackages
			for (String basePackage : basePackages) {

				// 扫描basePackages下所有的文件,进行include、exclude、@Conditional判断后返回BeanDefinition
				Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

				for (BeanDefinition candidate : candidates) {
					ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); // scope解析:单例与多例
					candidate.setScope(scopeMetadata.getScopeName());

					String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); // 生成beanName:默认是类名首字母小写,如果@Component中指定了name则使用指定的name

					/* ScannedGenericBeanDefinition extends GenericBeanDefinition(AbstractBeanDefinition) implements AnnotatedBeanDefinition  */
					// 设置BeanDefinition的默认值
					if (candidate instanceof AbstractBeanDefinition) {
						postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
					}
					if (candidate instanceof AnnotatedBeanDefinition) {
						// 解析@Lazy、@Primary、@DependsOn、@Role、@Description
						AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
					}

					// 检查Spring容器中是否已经存在该beanName(如果存在则抛出异常)
					if (checkCandidate(beanName, candidate)) {
						BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
						definitionHolder =
								AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
						beanDefinitions.add(definitionHolder);

						// 注册: beanDefinitionMap.put(beanName, beanDefinition)
						registerBeanDefinition(definitionHolder, this.registry);
					}
				}
			}
			return beanDefinitions;
		}

Spring扫描流程图

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@来杯咖啡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值