@EnableAutoConfiguration注解解析过程

前言

注解的处理、自动配置类的加载和条件注解的评估

  • 注解处理

在springapplication.run方法中,spring容器会解析主类上的注解。

创建ApplicationContext实例

在refreshContext方法中,初始化应用上下文,在这个过程中,会注册配置类。

onRefresh方法会处理@Import注解,从而导入AutoConfigurationImportSelector类

该类的slectImports方法被调用,从而导入自动配置类的列表

  • 条件注解(@ConditionalOnClass@ConditionalOnMissingBean)用于决定某个自动配置类是否应该被激活--ConditionEvaluator 类进行评估

@EnableAutoConfiguration 注解被 Spring 容器感知的过程包括以下几个关键步骤:

  1. 解析主类注解 :Spring 容器解析主类上的 @SpringBootApplication 注解,进而解析 @EnableAutoConfiguration 注解。
  2. 创建 ApplicationContextSpringApplication.run 方法创建 ApplicationContext 实例。
  3. 注册配置类ApplicationContextrefresh 方法注册配置类。
  4. 处理 @Import 注解@Import 注解导入 AutoConfigurationImportSelector 类。
  5. 调用 selectImports 方法AutoConfigurationImportSelector 类的 selectImports 方法返回符合条件的自动配置类列表。
  6. 条件注解的评估ConditionEvaluator 类评估条件注解,决定哪些自动配置类应该被激活。

springApplication构造函数中,会进行一些初始化操作:设置主类、加载初始器、监听器

run方法执行:

在 prepareContext 方法中,会注册主类及其注解

@处理SpringBootApplication注解

@处理EnableAutoConfiguration注解

@Import注解导入AutoConfigrationImportSelector类


  • SpringApplication 实例
arduino 复制代码
public static void main(String[] args) 
{ SpringApplication.run(MyApplication.class, args); }
  • 创建ApplicationContext实例
scss 复制代码
public ConfigurableApplicationContext run(String... args) {
    // 创建并初始化 SpringApplication 实例
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringApplicationRunListener> listeners = getRunListeners(args);
    for (SpringApplicationRunListener listener : listeners) {
        listener.starting();
    }
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext(); // 创建 ApplicationContext
        configureContext(context);
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context); // 刷新 ApplicationContext
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    } catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
}

3. 注册配置类

prepareContext 方法中,会注册主类及其注解。

scss 复制代码
Java
深色版本
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                            Collection<SpringApplicationRunListener> listeners, ApplicationArguments applicationArguments,
                            Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.started(context);
    callRunners(context, applicationArguments);
}

4. refresh 方法

refreshContext 方法中,会调用 ApplicationContextrefresh 方法,初始化应用上下文。

scss 复制代码
Java
深色版本
private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
}

5. refresh 方法内部

refresh 方法内部会调用多个子方法来完成应用上下文的初始化。其中,invokeBeanFactoryPostProcessors 方法会处理 BeanFactoryPostProcessor,包括 ConfigurationClassPostProcessor

scss 复制代码
Java
深色版本
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        } catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }
    }
}

6. invokeBeanFactoryPostProcessors 方法

invokeBeanFactoryPostProcessors 方法会调用 BeanFactoryPostProcessor,包括 ConfigurationClassPostProcessor

scss 复制代码
Java
深色版本
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

    // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
    // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
    if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        // Re-register post-processor for potential re-ordering
        beanFactory.removeBeanDefinition(LOAD_TIME_WEAVER_BEAN_NAME);
        registerBeanDefinition(LOAD_TIME_WEAVER_BEAN_NAME, new RootBeanDefinition(LoadTimeWeaverAwareProcessor.class));
    }
}

7. ConfigurationClassPostProcessor 处理

ConfigurationClassPostProcessor 是一个特殊的 BeanFactoryPostProcessor,它负责处理配置类。它会解析带有 @Configuration 注解的类,并处理其中的 @Import 注解。

kotlin 复制代码
Java
深色版本
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, Aware {
    // ...

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Reading annotations on " + registry.getClass().getName() + " bean definitions.");
        }
        processConfigBeanDefinitions(registry);
        this.registriesPostProcessed.add(registryId);
    }

    // ...
}

8. processConfigBeanDefinitions 方法

processConfigBeanDefinitions 方法会解析配置类,并处理其中的 @Import 注解。

scss 复制代码
Java
深色版本
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // ...
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(16);
    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 parsed as a configuration class: " + beanDef);
            }
        }
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            candidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }

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

    Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<>(candidates);
    configCandidates.addAll(this.deferredImportSelectorHandler.getCandidates());
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(bd, holder.getBeanName());
            }
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }

    // Process the parsed configuration classes
    Set<ConfigurationClass> candidates = new LinkedHashSet<>(asSet(this.configurationClasses.values()));
    candidates.addAll(this.deferredImportSelectorHandler.getCandidates());
    this.importRegistry.processDeferQueue();
    this.reader.loadBeanDefinitions(candidates);
}

9. parse 方法

parse 方法会解析配置类,并处理其中的 @Import 注解。

scss 复制代码
Java
深色版本
private void parse(AnnotatedBeanDefinition annotatedDef, String beanName) throws IOException {
    AnnotationMetadata annotationMetadata = annotatedDef.getMetadata();
    Class<?> beanClass = resolveBeanClass(annotatedDef, beanName);
    parse(annotationMetadata, beanClass, beanName);
}

private void parse(Class<?> beanClass, String beanName) throws IOException {
    parse(ClassUtils.getMetadata(beanClass), beanClass, beanName);
}

private void parse(AnnotationMetadata metadata, Class<?> beanClass, String beanName) throws IOException {
    String className = metadata.getClassName();
    if (this.knownSuperclasses.containsKey(className)) {
        return;
    }

    // Handle @Import annotations
    for (String importClassName : metadata.getAnnotationTypesByMetaAnnotationType(Import.class.getName())) {
        processImports(metadata, null, Collections.singleton(importClassName), false);
    }

    // Handle @ImportResource annotations
    AnnotationAttributes importResourceAnn = AnnotationConfigUtils.attributesFor(metadata, ImportResource.class);
    if (importResourceAnn != null) {
        String[] resources = importResourceAnn.getStringArray("locations");
        Class<? extends BeanDefinitionReader> readerClass = importResourceAnn.getClass("reader");
        for (String resource : resources) {
            String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
            this.reader.registerBeanDefinition(resolvedResource, beanName, readerClass);
        }
    }

    // Handle @Configuration classes
    if (ConfigurationClassUtils.isConfigurationCandidate(metadata) && !this.excludedSuperclasses.contains(className)) {
        ConfigurationClass configClass = new ConfigurationClass(metadata, beanClass, beanName);
        this.configurationClasses.put(className, configClass);
        this.knownSuperclasses.put(className, configClass);
        processConfigurationClass(configClass, beanName);
    }
}

10. processImports 方法

processImports 方法会处理 @Import 注解,调用 AutoConfigurationImportSelectorselectImports 方法。

scss 复制代码
Java
深色版本
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, boolean checkForCircularReferences) {
    if (importCandidates.isEmpty()) {
        return;
    }

    MultiValueMap<String, SourceClass> candidateImports = new LinkedMultiValueMap<>(importCandidates.size());
    for (SourceClass candidate : importCandidates) {
        if (candidate.isAssignable(ImportSelector.class)) {
            // Candidate class is an ImportSelector -> delegate to it to determine imports
            Class<?> candidateClass = candidate.loadClass();
            ImportSelector selector = (ImportSelector) instantiateClass(candidateClass);
            Predicate<String> predicate = metadataReader -> {
                AnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(metadataReader);
                return AnnotationUtils.isCandidateClass(annotationMetadata, selector.getImportFilterMarker());
            };
            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
            Set<String> importClasses = new LinkedHashSet<>(Arrays.asList(importClassNames));
            if (selector instanceof DeferredImportSelector) {
                DeferredImportSelector deferredSelector = (DeferredImportSelector) selector;
                importClasses = this.deferredImportSelectorHandler.handle(configClass, deferredSelector, importClasses);
            }
            for (String importClassName : importClasses) {
                if (this.importRegistry.isImportDuplicated(configClass, importClassName)) {
                    throw new BeanDefinitionStoreException("Detected cyclic processing of @Configuration " +
                            "class [" + configClass + "]; break the cycle by removing the offending " +
                            "@Import annotations");
                }
                if (checkForCircularReferences || AnnotationUtils.isCandidateClass(currentSourceClass.getMetadata(), ImportSelector.class)) {
                    this.importRegistry.registerImport(configClass, importClassName);
                }
                if (this.conditionEvaluator.shouldSkip(importClassName, ConfigurationPhase.REGISTER_BEAN)) {
                    continue;
                }
                candidateImports.add(importClassName, currentSourceClass);
            }
        }
        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
            // Candidate class is an ImportBeanDefinitionRegistrar ->
            // delegate to it to register additional bean definitions
            Class<?> candidateClass = candidate.loadClass();
            ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar) instantiateClass(candidateClass);
            ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
            if (registrar instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) registrar).setResourceLoader(this.resourceLoader);
            }
            if (registrar instanceof BeanDefinitionRegistryAware) {
                ((BeanDefinitionRegistryAware) registrar).setBeanDefinitionRegistry(this.registry);
            }
            if (registrar instanceof EnvironmentAware) {
                ((EnvironmentAware) registrar).setEnvironment(this.environment);
            }
            if (registrar instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) registrar).setBeanClassLoader(this.beanClassLoader);
            }
            if (registrar instanceof BeanFactoryAware) {
                ((BeanFactoryAware) registrar).setBeanFactory(this.registry);
            }
            if (registrar instanceof ImportBeanDefinitionRegistrarAdapter) {
                ((ImportBeanDefinitionRegistrarAdapter) registrar).setImportMetadata(currentSourceClass.getMetadata());
            }
            registrar.registerBeanDefinitions(currentSourceClass.getMetadata(), this.registry, enhancer);
        }
        else {
            // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
            // process it as an @Configuration class
            SourceClass updatedSourceClass = currentSourceClass;
            if (candidate.isAssignable(Configuration.class)) {
                updatedSourceClass = this.reader.loadSourceClass(candidate.loadClass(), currentSourceClass);
            }
            processConfigurationClass(updatedSourceClass.getMetadata(), updatedSourceClass.getMetadata().getClassName());
        }
    }

    if (!candidateImports.isEmpty()) {
        // Recursively process any further imports
        for (Map.Entry<String, List<SourceClass>> entry : candidateImports.entrySet()) {
            String importClassName = entry.getKey();
            Class<?> importClass = ClassUtils.resolveClassName(importClassName, this.beanClassLoader);
            AnnotationMetadata importMetadata = AnnotationMetadata.introspect(importClass);
            for (SourceClass sourceClass : entry.getValue()) {
                processImports(configClass, sourceClass, this.metadataReaderFactory, importMetadata, checkForCircularReferences);
            }
        }
    }
}

11. selectImports 方法

AutoConfigurationImportSelector 类的 selectImports 方法会被调用,返回一个包含所有符合条件的自动配置类的列表。

typescript 复制代码
Java
深色版本
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return (autoConfigurationEntry != null) ? autoConfigurationEntry.getConfigurationClasses() : NO_IMPORTS;
}

总结

  1. 启动 Spring Boot 应用 :调用 SpringApplication.run 方法。
  2. 创建 ApplicationContext :创建并初始化 ApplicationContext
  3. 注册配置类:注册主类及其注解。
  4. 调用 refresh 方法 :刷新 ApplicationContext
  5. 处理 BeanFactoryPostProcessor :调用 invokeBeanFactoryPostProcessors 方法。
  6. 处理 ConfigurationClassPostProcessor:解析配置类。
  7. 处理 @Import 注解 :调用 processImports 方法。
  8. 调用 selectImports 方法AutoConfigurationImportSelector 类的 selectImports 方法返回符合条件的自动配置类列表。
相关推荐
gCode Teacher 格码致知20 分钟前
《Asp.net Mvc 网站开发》复习试题
后端·asp.net·mvc
Moshow郑锴2 小时前
Spring Boot 3 + Undertow 服务器优化配置
服务器·spring boot·后端
Chandler243 小时前
Go语言即时通讯系统 开发日志day1
开发语言·后端·golang
有梦想的攻城狮3 小时前
spring中的@Lazy注解详解
java·后端·spring
野犬寒鸦4 小时前
Linux常用命令详解(下):打包压缩、文本编辑与查找命令
linux·运维·服务器·数据库·后端·github
huohuopro4 小时前
thinkphp模板文件缺失没有报错/thinkphp无法正常访问控制器
后端·thinkphp
cainiao0806057 小时前
《Spring Boot 4.0新特性深度解析》
java·spring boot·后端
-曾牛7 小时前
Spring AI 与 Hugging Face 深度集成:打造高效文本生成应用
java·人工智能·后端·spring·搜索引擎·springai·deepseek
南玖yy8 小时前
C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )
c语言·开发语言·c++·笔记·后端·游戏引擎·课程设计
计算机学姐8 小时前
基于SpringBoot的小区停车位管理系统
java·vue.js·spring boot·后端·mysql·spring·maven