Spring---组件扫描(如何找到一个组件并将其添加到容器)

SpringIOC就是将对象的创建过程交给Spring容器,Spring容器在启动的时候会扫面所有的组件(带有@Component、@Bean等注解)存放在beanDefinationMap中,然后再需要创建对象的时候,取出bean定义信息,实例化对象即可。这里我们将分析一下组件扫描的过程。

注意:本文的中心思想是看Spring如何扫描到某个组件,并将其添加到容器中,其余问题不探究,不要走偏。

一、分析

从容器初始化入口出发

Spring初始化即容器的初始化,容器初始化的入口在AbstractApplicationContext.refresh()方法:

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		prepareRefresh();	
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
		prepareBeanFactory(beanFactory);
		//添加BeanPostProcessor到BeanFactory,用于在Bean初始化前后包装Bean
		postProcessBeanFactory(beanFactory);
		//调用BeanFactoryPostProcessor,所有的bean定义将在这里加载,这是我们这节关注的重 
		//点
		invokeBeanFactoryPostProcessors(beanFactory);
		registerBeanPostProcessors(beanFactory);
		initMessageSource();
		initApplicationEventMulticaster();
		onRefresh();
		registerListeners();
		finishBeanFactoryInitialization(beanFactory);
		finishRefresh();
		resetCommonCaches();
	}
}

在invokeBeanFactoryPostProcessors的调用链中,其会从已有的容器中找到满足执行条件的所有BeanDefinitionRegistryPostProcessor类

调用其postProcessBeanDefinitionRegistry方法注册所有组件到容器中。这里你可以通过断掉调试,最终执行扫描所有类文件的BeanDefinitionRegistryPostProcessor类就是ConfigurationClassPostProcessor。、

那ConfigurationClassPostProcessor类是从哪里来的呢?其流程如下:

1.创建ApplicationContext的时候,优先会在构造函数中创建一个DefaultListableFactory容器。

public GenericApplicationContext() {
		this.beanFactory = new DefaultListableBeanFactory();
	}

2.同时创建ApplicationContext的时候,创建AnnotatedBeanDefinitionReader对象(假设是Web环境)

public AnnotationConfigServletWebServerApplicationContext(
			DefaultListableBeanFactory beanFactory) {
		super(beanFactory);
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

3.创建AnnotatedBeanDefinitionReader对象时,注册ConfigurationClassPostProcess对象,对于解析各种组件注解。

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

org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)中代码如下:

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

 

至此,我们可以知道容器在refresh过程中,会调用钩子函数org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry注册beanDefinition到beanDefinitionMap之中,而具体扫描程序中含有@Component、@Bean等注解的类将其转换为beanDefinition并添加到容器中的实现类为ConfigurationClassPostProcess。接下来我们主要分析该类是如何做的?

ConfigurationClassPostProcess分析

该类中主要通过org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法扫描,该方法的主要步骤分为:

1.找到候选需要解析的类

//这里如果我们是通过SpringBoot程序启动的,那么在SpringBoot.run方法中会将启动类加载到容器中
//而我们的启动类都会添加@SpringBootApplication注解,该注解包含@Configuration注解
String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
            //该方法中会检测对应的bean类是否存在@Configuration注解,存在则加入候选解析类
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

2.从我们的SpringBoot启动类开始解析

    // Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	Set candidates = new LinkedHashSet<>(configCandidates);
	Set alreadyParsed = new HashSet<>(configCandidates.size());
	do {
        //解析候选类,即根据启动类找到带有@Compontent注解的类
		parser.parse(candidates);
		parser.validate();

		Set 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());
		}
        //利用reader加载所有的.class文件,生成beanDefinition放入beanDefinitionMap
		this.reader.loadBeanDefinitions(configClasses);
		alreadyParsed.addAll(configClasses);

		candidates.clear();
		if (registry.getBeanDefinitionCount() > candidateNames.length) {
			String[] newCandidateNames = registry.getBeanDefinitionNames();
			Set oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
			Set 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());

3.ConfigurationClassParser 解析过程

主要调用该方法解析org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass:

//初始加载时,configClass为我们的启动类
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        //加载该bena定义的前置校验
		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);
	}

该方法org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass中会依次处理@Import,@ImportResource,@ComponentScan、方法上的@Bean 所指向位置的组件。比如我们所熟悉的@CompotentScan:

// Process any @ComponentScan annotations
        //读取该类上的所有@ComponentScan注解
		Set 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
                //利用ComponentScanParser解析@ComponentScan注解
				Set 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) {
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(
							holder.getBeanDefinition(), this.metadataReaderFactory)) {
						parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

4.利用ComponentScanParser解析@ComponentScan注解

该方法的主要处理逻辑为:

1.找到basePackages注解,即要扫描的类的包范围

Set basePackages = new LinkedHashSet<>();
	String[] basePackagesArray = componentScan.getStringArray("basePackages");
	for (String pkg : basePackagesArray) {
		String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		basePackages.addAll(Arrays.asList(tokenized));
	}
	for (Class clazz : componentScan.getClassArray("basePackageClasses")) {
		basePackages.add(ClassUtils.getPackageName(clazz));
	}

2.如果没有配置,则取启动类所在包

if (basePackages.isEmpty()) {
		basePackages.add(ClassUtils.getPackageName(declaringClass));
}

3.利用ClassPathBeanDefinitionScannerClas扫描包下所有类,解析成beanDefinition,具体方法在org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents,有兴趣的可以研究研究。

二、总结

其实整个过程并不难理解:

1.Spring启动的时候,创建ApplicationContext,同时创建钩子类ConfigurationClassPostProcessor。

2.在调用refresh时候,调用钩子方法ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry做扫描工作。

3.在钩子方法中,从SpringBoot启动类开始,寻找basePackage指定扫描包,如果找寻不到,取当前启动类所在包,加载包下所有的.class文件。

4.对于带有@Component注解的类,加载进来生成beanDefinition添加到容器当中。

你可能感兴趣的:(杂记---框架)