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 的基礎上,我們可以知道,在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為配置類的邏輯為
- 判斷Class是否包含@Configuration註解,如果包含,為配置類
- 如果沒有,@Component,@ComponentScan,@Import,@ImportResource,檢視是否包含此4個註解之一,如果包含,為配置類
- 如果沒有,判斷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註解,此註解配置的型別可以有三種:
- ImportSelector介面型別,可以看做一個匯入選擇器,返回多個要匯入的Class型別,如SpringBoot自動裝配的實現AutoConfigurationImportSelector。
- ImportBeanDefinitionRegistrar,可以看做一個註冊器,Spring提供的一個鉤子,可以讓我們向BeanDefinitionRegistry中新增自定義的BeanDefinition,
如開啟AOP的AspectJAutoProxyRegistrar。 - 其他型別的配置類
接下來繼續分析配置類的載入,進入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框架的核心有兩個:
- 註冊BeanDefinition到容器中
從各種渠道註冊,如XML配置檔案,@Component註解,@Bean註解,手動建立BeanDefinition註冊,各種擴充套件類如ImportBeanDefinitionRegistrar的註冊。 - 根據BeanDefinition建立Bean物件
建立過程中,可能會建立代理物件,這就是AOP的功能。
Spring很多附加的功能都是通過幫我們自動註冊了很多BeanDefinition來完成的。