1. 程式人生 > 其它 >Spring原始碼分析之@Configuration註解的處理

Spring原始碼分析之@Configuration註解的處理

前言

Spring從3.0開始支援JavaConfig配置,具體來說就是可以完全通過註解來開啟Bean掃描,宣告Bean,匯入properties檔案等。
主要有以下註解:
@Configuration: 標識此Bean是一個配置類,接下來開始解析此類
@ComponentScan: 開啟註解掃描,預設掃描@Component註解
@Import: 匯入其他配置類
@ImportResource: 匯入其他XML配置檔案
@Bean: 在方法上使用,宣告此方法為一個Bean

簡單使用

import java.util.ArrayList;
import java.util.LinkedList;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class TestConfiguration {

  public static void main(String[] args) {
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(BeanConfig.class);
    //註冊BeanConfig類
    beanFactory.registerBeanDefinition("beanConfig", builder.getBeanDefinition());
    //處理@Configuration註解
    ConfigurationClassPostProcessor configurationClassPostProcessor = new ConfigurationClassPostProcessor();
    configurationClassPostProcessor.postProcessBeanDefinitionRegistry(beanFactory);
    configurationClassPostProcessor.postProcessBeanFactory(beanFactory);
    System.out.println(beanFactory.getBean("beanConfig").getClass());
    System.out.println(beanFactory.getBean("myArrayList").getClass());
    System.out.println(beanFactory.getBean(LinkedList.class).getClass());
  }

  @Configuration(proxyBeanMethods = true)
  @ComponentScan
  @Import({MyBeanRegistrar.class, MySelector.class})
  public static class BeanConfig {

  }

  public static class MyBeanRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
        BeanDefinitionRegistry registry) {
      BeanDefinitionBuilder builder = BeanDefinitionBuilder
          .genericBeanDefinition(ArrayList.class);
      registry.registerBeanDefinition("myArrayList", builder.getBeanDefinition());
    }
  }

  public static class MySelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
      return new String[]{LinkedList.class.getName()};
    }
  }
}

這裡為了更好的分析原理,沒有使用更加強大的ApplicationContext,核心類為ConfigurationClassPostProcessor,
這是一個BeanDefinitionRegistryPostProcessor(BeanDefinitionRegistry後置處理器,可以讓對BeanDefinitionRegistry進行擴充套件處理,如新增自定義的BeanDefinition),
也是一個BeanFactoryPostProcessor(BeanFactory後置處理器,可以讓我們擴充套件BeanFactory)。
ConfigurationClassPostProcessor會判斷Bean是否為一個配置類,如果是,就解析此類,具體就是解析@ComponentScan,@Import等註解。
如果我們使用支援JavaConfig的ApplicationContext,它會通過AnnotationConfigUtils的registerAnnotationConfigProcessors()方法來自動新增ConfigurationClassPostProcessor類。
ApplicationContext會在refresh()方法執行過程中處理ConfigurationClassPostProcessor的後置方法,
關於ApplicationContext,可以檢視

Spring原始碼分析之ApplicationContext

原始碼分析

Spring原始碼分析之ApplicationContext 的基礎上,我們可以知道,在refresh()方法的步驟invokeBeanFactoryPostProcessors()中,
會執行BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法,
然後再執行BeanFactoryPostProcessor的postProcessBeanFactory()方法,所以我們先分析ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()方法。

postProcessBeanDefinitionRegistry

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	        //處理配置
		processConfigBeanDefinitions(registry);
	}

繼續跟進去

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
                //過濾出所有Bean中為配置類的Bean,下面會說判斷的條件
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		//沒有配置類
		if (configCandidates.isEmpty()) {
			return;
		}

		//按照優先順序排序
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		//從容器中查詢Bean名稱生成器
		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;
				}
			}
		}

                //建立environment 
		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		//配置類解析器
		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 {
                        //真正開始解析
			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());
			}
                        //從解析好的配置類中載入BeanDefinition,註冊到容器中
			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());

		//將ImportRegistry註冊為一個Bean,用來支援ImportAware鉤子回撥
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}
	}

判斷一個Bean為配置類的邏輯為

  1. 判斷Class是否包含@Configuration註解,如果包含,為配置類
  2. 如果沒有,@Component,@ComponentScan,@Import,@ImportResource,檢視是否包含此4個註解之一,如果包含,為配置類
  3. 如果沒有,判斷Class是否有方法包含@Bean註解

上述邏輯彙總,核心地方有兩個,一個是解析配置類,一個是載入配置類,先看解析,進入ConfigurationClassParser解析器

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
                                //根據不同的BeanDefinition型別呼叫不同的方法,最後解析時會統一處理
				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());
				}
			}
		}
                //這個處理也是很重要的,延遲匯入,SpringBoot提供的AutoConfigurationImportSelector就是一個延遲載入的匯入選擇器,
                //它會在我們我們自己的配置類載入之後再載入,相當於低優先順序,
                //因為在處理OnMissingBeanCondition等註解時需要依賴前面的配置類來判斷某個Bean是否已經在容器中存在
		this.deferredImportSelectorHandler.process();
	}

不同的BeanDefinition型別,都會統一建立一個ConfigurationClass來處理,繼續跟進去

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);
				}
				return;
			}
			else {
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		//遞迴處理配置類及父類
		SourceClass sourceClass = asSourceClass(configClass);
		do {
                        //核心,解析配置類
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);

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

開始真正的處理解析配置類

@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {

		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			//處理內部類的情況
			processMemberClasses(configClass, sourceClass);
		}

		//解析@PropertySource註解,用來處理properties檔案,新增到environment中
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
		}

		//解析@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) {
				//內部使用ClassPathBeanDefinitionScanner掃描器,預設掃描@Component註解,注意,掃描完成已經將BeanDefinition註冊到容器中了
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				//如果掃描到的BeanDefinition也包含配置類,遞迴解析配置類
				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註解
		processImports(configClass, sourceClass, getImports(sourceClass), true);

		//解析@ImportResource註解,可以匯入XML配置檔案
		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註解的方法
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		//處理介面相關,不用管
		processInterfaces(configClass, sourceClass);

		//處理父類
		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();
			}
		}

		//沒有父類,處理結束
		return null;
	}

關於解析@Import註解,此註解配置的型別可以有三種:

  1. ImportSelector介面型別,可以看做一個匯入選擇器,返回多個要匯入的Class型別,如SpringBoot自動裝配的實現AutoConfigurationImportSelector。
  2. ImportBeanDefinitionRegistrar,可以看做一個註冊器,Spring提供的一個鉤子,可以讓我們向BeanDefinitionRegistry中新增自定義的BeanDefinition,
    如開啟AOP的AspectJAutoProxyRegistrar。
  3. 其他型別的配置類

接下來繼續分析配置類的載入,進入ConfigurationClassBeanDefinitionReader的loadBeanDefinitions()方法

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
                //使用一個支援追蹤的條件解析器來判斷配置類是否可以載入,如果A配置類是被B配置類通過@Import註解引入的,B配置類不載入,那麼A配置類也不能被載入
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
                        //依次載入每一個配置類
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

繼續

private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
                //如果不能被載入,從容器中刪除
		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}
                //如果此配置類是被匯入的,註冊此配置類到容器中
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
                //註冊所有包含@Bean註解的方法到容器中,這種Bean通過工廠方法來例項化
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}
                //使用XmlBeanDefinitionReader從XML配置檔案中載入所有Bean,其實還支援groovy型別的配置檔案,用的不多,就先不管了
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
                //從註冊器載入,依次呼叫所有ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

至此Spring已經將所有的BeanDefinition都註冊到容器中了。

postProcessBeanFactory

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		//使用CGLIB對配置類建立動態代理
		enhanceConfigurationClasses(beanFactory);
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	}

通過@Configuration(proxyBeanMethods = true)來標記開啟代理,預設就是true,主要是為了處理下面這種情況

@Configuration(proxyBeanMethods = true)
public static class BeanConfig {
    @Bean("myHashSet")
    public Set<String> myHashSet() {
      return new HashSet<>();
    }
    @Bean("myArrayList")
    public List<String> myArrayList() {
      return new ArrayList<>(myHashSet());
    }
    @Bean("myLinkedList")
    public List<String> myLinkedList() {
      return new LinkedList<>(myHashSet());
    }
  }

在配置類中聲明瞭3個Bean,按理來說只會建立一個名稱為myHashSet的Bean,但myArrayList()和myLinkedList方法內部都呼叫了myHashSet()方法,不能建立兩個myHashSet的Bean,
這就是因為Spring對配置類建立了動態代理物件,當呼叫myHashSet()方法時,會根據方法找到對應的Bean名稱,從容器中查詢出對應的Bean物件。

分析總結

Spring處理配置主要有以下幾個類:

  • ConfigurationClassPostProcessor: 框架類,使用下面的幾個類來完成解析,載入
  • ConfigurationClassParser: 解析所有配置類
  • ConfigurationClassBeanDefinitionReader: 載入所有配置類
  • ConfigurationClassEnhancer: 根據需要對配置類建立代理

Spring框架的核心有兩個:

  1. 註冊BeanDefinition到容器中
    從各種渠道註冊,如XML配置檔案,@Component註解,@Bean註解,手動建立BeanDefinition註冊,各種擴充套件類如ImportBeanDefinitionRegistrar的註冊。
  2. 根據BeanDefinition建立Bean物件
    建立過程中,可能會建立代理物件,這就是AOP的功能。

Spring很多附加的功能都是通過幫我們自動註冊了很多BeanDefinition來完成的。