@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 方法返回符合条件的自动配置类列表。
相关推荐
Zane19945 小时前
只改一个方向的引用,循环引用就能立刻被回收?一文讲透 weakref 弱引用
后端·python
长大19885 小时前
窗口函数用不好反而更慢?SQL Server中OVER子句的4个性能陷阱
后端
大黄评测5 小时前
MERGE语句有Bug?SQL Server官方不推荐的背后真相与替代方案
后端
长大19885 小时前
TempDB 爆满导致系统卡死?SQL Server TempDB 瓶颈诊断与根治方案
后端
lichenyang4535 小时前
从「房间」到「实时通知」:用 NestJS + Socket.IO 实现团队邀请的完整工程实践
前端·后端
大黄评测5 小时前
为什么你的SQL查询慢?这7个执行计划陷阱90%的人都踩过
后端
沙盘客5 小时前
AFSIM 示例解读(09)· 传感器全家桶 sensor_demos(下):ESM / SAR / 被动测向
c++·经验分享·后端
叫我Paul就好5 小时前
Spring 为何没有在 Java之外的地方存在?
java·后端·spring
掘金酱6 小时前
TRAE Work 实战帮征文 | 获奖名单公示
前端·人工智能·后端
用户69371750013846 小时前
前阵子刷屏的 Ox-Alpha 真身揭晓!GLM-5.3-Flash 上线,这价格真的杀疯了
前端·后端·github