Spring源码系列二:扫描Bean

Spring源码系列二:扫描Bean

更多请关注:https://t.zsxq.com/fhroW

扫描

找到需要spring管理的类,并存入到beanDefinitionMap中。主要步骤如下:

  • 步骤一:扫描出需要spring管理的类,封装为BeanDefinition
  • 步骤二:填充BeanDefinition的属性
  • 步骤三:判断BeanName是否重复
  • 步骤四:注册Bean

源码查看

去除了日志打印、参数非空判断、异常抛出

  • 扫描入口:
ClassPathBeanDefinitionScanner.scan(String... basePackages)
  • scan()
    传入包扫描路径,返回最后扫描到的Bean数量,主要看doScan方法
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
	doScan(basePackages);

	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registr);
	}
	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
  • doScan()
  • 步骤一:见下文
  • 步骤二
    经过步骤一之后BeanDefinition中只有一个class名称,没有其他属性,步骤二就是填充属性,如BeanName、是单例还是多例、是否懒加载等
    步骤三
    检查Spring容器中是否已经存在该beanName,如果存在相同的BeanName且两个类不相同则抛出异常,开发中常见的BeanName重复就是在这里抛出的
    步骤四
    注册Bean ,即将BeanDefinition添加到beanDefinitionMap中
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {
		
		//步骤一:扫描出所有的BeanDefinition
		Set<BeanDefinition> candidates =findCandidateComponents(basePackage);
		
		//步骤二:填充BeanDefinition的属性
		for (BeanDefinition candidate : candidates) {
			ScopeMetadata scopeMetadata =this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			//从Component中取value值,如果没有就调用JDK的方法生成名字
			String beanName =this.beanNameGenerator.generateBeanName(candidate,this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate,beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
				// 解析@Lazy、@Primary、@DependsOn、@Role、@Description
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnotatedBeanDefinition) candidate);
			}
			
			//步骤三:判断BeanName是否重复
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = newBeanDefinitionHolder(candidate, beanName);
				definitionHolder =
					AnnotationConfigUtils.applyScopedProxyMode(scopeMetaata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				
				//步骤四:注册Bean,这里的this.registery就是scan方法中的registry
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

步骤一,扫描出需要spring管理的类,封装为BeanDefinition

  • 1.1: 加载basePackage下所有的文件资源,得到resources
  • 1.2: 遍历resources创建每个类的元数据读取器
    元数据读取器中包含:类名称、类上的注解、方法上的注解、实现的口名称、继承的类名称、内部类列表等信息
  • 1.3: 判断该类是否匹配排除过滤器和包含过滤器
    spring默认添加了一个包含过滤器,如果该类有Component注解,就会匹配包含过滤器
  • 1.4 判断是否为独立的类、接口、抽象类。非独立类、接口、抽象类都不创建Bean
    其中抽象类中如果有方法贴了注解@Lookup注解,该接口也会创建Bean(见拓展)
    什么是独立的类?最顶层的类和静态内部类是独立的类,其他不,比如非静态的内部类

判断有无/resources/META-INF/spring.components索引文件。如果存在索引文则从改文件中扫描Bean,就不从Target中去扫描了,这样更快

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
	if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
		return addCandidateComponentsFromIndex(this.componentsIndex,basePackage);
	}
	else {
		return scanCandidateComponents(basePackage);
	}
}

没有索引文件的情况,从Target中去扫描

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
	Set<BeanDefinition> candidates = new LinkedHashSet<>();
	
	//(1.1)获取basePackage下所有的文件资源
	String packageSearchPath = "classpath*:" +resolveBasePackage(basePackage) + "/**/*.class";
	Resource[] resources =getResourcePatternResolver().getResources(packageSearchPath);   //D:/package/workspace/.../com/xxx/AppConfig.class
	for (Resource resource : resources) {
		if (resource.isReadable()) {
		
			//(1.2):遍历resources创建每个类的元数据读取器
			MetadataReader metadataReader =getMetadataReaderFactory().getMetadataReader(resource);
			
			// (1.3):excludeFilters、includeFilters判断
			if (isCandidateComponent(metadataReader)) { 
				//生成BeanDefinition,通过扫描生成的就是ScannedGenericBeanDeinition,还有其他类型,比如通过XML配置的就是XmlBeanDefinitioReader
				ScannedGenericBeanDefinition sbd = newScannedGenericBeanDefinition(metadataReader);
				sbd.setSource(resource);
				
				//(1.4):判断是否为独立的类,是否为接口、抽象类。
				if (isCandidateComponent(sbd)) {
					candidates.add(sbd);
				} 
			}
		}
	}
	return candidates;
}

拓展

  • JFR:jdk提供的监控程序运行的性能参数,例如执行时间
  • @Lookup注解
    使用场景:
@Component
@Scope("prototype")
public class User{}

@Component
public class Person(){
    @Autowire
    private User user;

    public void test(){
        System.out.println(user);
    }
}

public class Test {

	public static void main(String[] args) {

		// 创建一个Spring容器
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

		Person person = (Person) applicationContext.getBean("person");
		person.test();
        person.test();
        person.test();
	}
}

以下代码打印出的三个user是同一个对象还是三个对象?是一个对象。尽管User是一个多例对象,但是Person是单例的,创建Person进行依赖注入时已经把User创建好了,所以使用user属性时不会再创建
要想实现多例的效果,就可以使用@Lookup注解:

@Component
public class Person(){
    @Autowire
    private User user;

    public void test(){
        System.out.println(anyName());
    }

    @Lookup
    public User anyName(){
        return any;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值