解析@Import底层原理

本文详细解析了Spring框架中@Import注解的工作原理,从配置类的解析过程开始,逐步深入到ConfigurationClassPostProcessor、ConfigurationClassParser、doProcessConfigurationClass和processImports方法的执行细节。讲解了ImportSelector和ImportBeanDefinitionRegistrar接口的使用,以及如何通过这两个接口自定义向容器注册组件。最后通过测试类展示了实际应用。

目录

一、概述

 二、底层解析

1、ConfigurationClassPostProcessor

2、ConfigurationClassParser

 3、doProcessConfigurationClass

 4、processImports

4.1 ImportSelector

 4.2 ImportBeanDefinitionRegistrar

  4.3 普通组件类

 三、测试类


一、概述

@Import用来向ioc容器注册组件。

注意:@Import只是向容器注册添加组件的相关信息,组件还未实例化,后续由容器进行实例化。

value数组值:

普通组件类、实现ImportSelector接口的实现类、实现ImportBeanDefinitionRegistrar接口的实现类

1、普通组件类:直接向容器注册组件,组件的id为组件的全限定类名,若是组件是一个配置类,则配置类内的组件(若配置类定义了内部类,也会扫描内部类判断是否加载该内部类)也会被注册到容器中

2、实现ImportSelector接口的实现类:通过实现selectImports方法返回组件的全限定类名数组,组件的id为组件的全限定类名

public class MyImportSelector implements ImportSelector {
    /**
     *
     * @param importingClassMetadata 当前标识Import注解的类的所有注解信息
     * @return 返回组件的全类名
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.mzp.component.service.Red"};
    }
}

3、实现ImportBeanDefinitionRegistrar接口的实现类:通过实现registerBeanDefinitions方法,可自定义向容器注册组件,可指定组件id

public class  MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     *
     * @param importingClassMetadata 标识Import注解的类的所有注解信息
     * @param registry Bean定义的注册类
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainRow.class);
        registry.registerBeanDefinition("rain",rootBeanDefinition);
    }
}

 二、底层解析

1、ConfigurationClassPostProcessor

用来解析标识@Configuration配置类。

创建ioc容器时将ConfigurationClassPostProcessor注册到容器中,这是一个BeanDefinitionRegistryPostProcessor实现类,refresh方法中运行invokeBeanDefinitionRegistryPostProcessors方法时,调用ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法里的processConfigBeanDefinitions方法去解析容器已注册的标识@Configuration的配置类

 processConfigBeanDefinitions方法创建ConfigurationClassParser对象去解析@Configuration配置类的信息,包括解析注解@Import信息

2、ConfigurationClassParser

parse方法中调用processConfigurationClass方法

 processConfigurationClass方法通过循环调用doProcessConfigurationClass方法来解析当前配置类和配置类的父类信息

 3、doProcessConfigurationClass

doProcessConfigurationClass方法解析多个spring注解,例如

@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean、解析内部类信息等

解析内部类

 定位到如图processImports方法,该方法就是实际用来解析@Import注解

 getImports方法返回所有@Import注解的value集合,getImports方法通过collectImports方法获取当前组件标识@Import的value集合,包括元注解内部的@Import的value集合

 4、processImports

processImports方法中遍历已得到@Import的value集合importCandidates,判断每个value属于哪种类型

4.1 ImportSelector

若是value实现了ImportSelector接口,则先通过容器工具类创建对象,然后调用对象的selectImports方法得到导入组件数组importClassNames,继续递归调用processImports方法解析每个导入组件

 4.2 ImportBeanDefinitionRegistrar

若是value实现了ImportBeanDefinitionRegistrar接口, 创建该对象,将对象添加到当前类的importBeanDefinitionRegistrars集合中

 在后续解析完所有的@Configuration配置类后,调用ConfigurationClassPostProcessorloadBeanDefinitions方法注册每个配置组件类到容器时,

 调用loadBeanDefinitionsForConfigurationClass方法loadBeanDefinitionsFromRegistrars方法运行当前类的importBeanDefinitionRegistrars集合的registerBeanDefinitions方法

  4.3 普通组件类

当前value都没实现ImportSelectorImportBeanDefinitionRegistrar接口时,将它当成一个配置类继续调用processConfigurationClass方法解析该配置类

 三、测试类

@Test
    public void test15(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ImportConfig.class);

    }
package com.mzp.component.myimport;

import com.mzp.component.service.RainRow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class  MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     *
     * @param importingClassMetadata 标识Import注解的类的所有注解信息
     * @param registry Bean定义的注册类
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainRow.class);
        registry.registerBeanDefinition("rain",rootBeanDefinition);
    }
}
package com.mzp.component.myimport;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;


public class MyImportSelector implements ImportSelector {
    /**
     *
     * @param importingClassMetadata 当前标识Import注解的类的所有注解信息
     * @return 返回组件的全类名
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.mzp.component.service.Red"};
    }
}
package com.mzp.component.myimport;

public class Test {
    public Test(){
        System.out.println("Test()");
    }
}
package com.mzp.component.config;

import com.mzp.component.myimport.MyImportBeanDefinitionRegistrar;
import com.mzp.component.myimport.MyImportSelector;
import com.mzp.component.myimport.Test;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(value = {Test.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class ImportConfig {

}

<think>好的,我现在要详细分析用户关于Spring框架中@Configuration注解的底层实现原理的问题。首先,我需要回顾自己已有的知识,并确保理解正确。 用户提到的是@Configuration注解的底层原理和实现机制。根据之前的引用内容,比如引用[1]和引用[3],其中提到@Configuration是Spring框架提供的注解,用于配置类,而@SpringBootConfiguration是它的派生。同时,@Configuration与@Bean注解结合使用,由ConfigurationClassParser处理配置类,生成Bean定义,并通过CGLIB代理增强。 首先,我应该从配置类的解析过程开始。Spring在启动时会扫描所有带有@Configuration的类,这通常是通过组件扫描实现的。然后,使用ConfigurationClassParser来解析这些类,识别其中的@Bean方法、@Import注解等。这一步可能涉及到递归处理,因为一个配置类可能导入其他配置类。 接下来,生成Bean定义。每个@Bean方法会被解析为一个BeanDefinition,并注册到BeanFactory中。这里需要注意的是,@Configuration注解的类本身也会被注册为一个Bean,因为可能需要被其他配置引用,或者被代理以处理方法调用。 然后,代理机制是关键。普通的@Bean方法在调用时可能会多次创建实例,而使用@Configuration的类通过CGLIB代理,确保@Bean方法之间的调用会被拦截,从而返回同一个实例,保证单例行为。这部分需要解释CGLIB如何生成子类,覆盖@Bean方法,并委托给父类的方法,同时通过BeanFactory来获取已有的实例。 还要提到配置类的增强阶段,在AbstractApplicationContext的refresh()方法中,会调用PostProcessorRegistrationDelegate来处理BeanFactoryPostProcessor,其中ConfigurationClassPostProcessor负责处理配置类。这时候会进行解析、增强,并生成代理对象。 可能的疑问点:如果不使用@Configuration而使用@Component,@Bean方法之间的直接调用不会经过代理,导致单例失效。这一点需要强调两者的区别,说明代理的重要性。 此外,需要结合引用中的内容,比如引用[3]提到的ConfigurationClassParser和代理对象生成,引用[1]中的@EnableAutoConfiguration可能与配置类的处理有关联,但用户的问题集中在@Configuration本身,所以可能需要更聚焦。 还要考虑用户可能的问题,比如配置类的加载顺序,如何处理嵌套的@Import,或者如何处理@Profile和@Conditional等条件注解。但用户当前的问题主要是底层机制,所以先集中在解析、Bean定义生成、代理这三个方面。 总结步骤应该是: 1. 配置类的识别与解析:如何被扫描,解析成配置类。 2. Bean定义的生成:将@Bean方法转化为BeanDefinition。 3. 代理机制:CGLIB如何确保单例,方法调用的拦截。 4. 与其他注解(如@Import)的交互。 在回答时,要结构清晰,分点说明,并引用提供的引用内容,如引用[3]提到ConfigurationClassParser,引用[1]中的@Configuration和@SpringBootConfiguration的关系。 另外,用户可能混淆@Configuration和@Component,需要指出它们的区别,尤其是代理机制带来的行为差异。 现在需要组织语言,确保使用正确的中文术语,并正确应用LaTeX格式,例如类名如`ConfigurationClassPostProcessor`用行内代码格式,但根据用户要求,数学公式用$...$,但代码应用反引号或代码块。不过用户示例中的代码块使用了python,这里可能需要用Java示例,但用户问题不涉及代码示例,所以可能不需要。但要注意LaTeX的正确使用,如类名可能不需要数学公式,所以可能不用$符号。但用户示例中的变量如$x^2$用了$...$,所以需要区分代码和数学内容。比如,如果提到类名,用反引号,如`@Configuration`,而如果提到方法调用或Bean名称,可能用行内代码。但根据用户系统级指令,行内数学表达式需要用$...$,但代码相关的内容可能属于另一种格式。需要再检查用户的指令: 用户要求所有行内数学表达式必须用$...$,而独立公式用$$...$$。代码需要用代码块格式,如示例中的Python代码用三个反引号包围。因此,在回答中,关于Spring的类名、方法名等应该用行内代码,如`@Configuration`,而数学表达式如变量等用$...$。但在这个问题中,可能数学表达式较少,主要是代码和类名,所以正确应用反引号即可。 现在整理回答的结构: 1. 配置类的识别与解析 - Spring启动时通过组件扫描识别带有@Configuration的类 - 使用ConfigurationClassParser解析配置类中的@Bean、@Import等注解 2. Bean定义的注册 - 每个@Bean方法生成BeanDefinition并注册到容器 - 处理@Import引入的其他配置类或普通类 3. CGLIB代理机制 - 为什么需要代理:保证@Bean方法间的调用被拦截,维持单例 - 代理类如何生成,覆盖@Bean方法,调用父类方法前检查Bean是否存在 4. 与其他机制的结合 - 如@Profile、@Conditional在解析时的处理 - 配置类处理的阶段(BeanFactoryPostProcessor阶段) 在引用部分,根据提供的引用内容,引用[3]提到了ConfigurationClassParser和代理对象生成,引用[1]提到@Configuration和@SpringBootConfiguration的关系。可以适当引用这些内容,例如在解析配置类时提到引用[3]的内容,或者在代理生成时引用[3]。 例如: “Spring使用ConfigurationClassParser解析配置类(引用[3])。” 此外,关于启动日志中的异常处理,引用[4]可能涉及配置类初始化时的错误处理,但可能与当前问题关系不大,所以可能不需要引用。 现在,需要确保回答符合用户的要求,结构清晰,分点说明,并正确引用提供的引用内容。同时,生成相关问题,可能需要涉及配置类与其他注解的交互、性能优化、常见问题等。</think>### Spring框架中@Configuration注解的底层实现原理 #### 1. 配置类的识别与解析 Spring在启动时通过**组件扫描**识别所有标注`@Configuration`的类。这些类被归类为“配置类”,并由`ConfigurationClassParser`进行解析[^3]。解析过程包括以下步骤: - 识别`@Bean`方法:将每个方法解析为一个独立的`BeanDefinition`,并注册到`BeanFactory`。 - 处理`@Import`注解:递归解析导入的其他配置类或普通类(如`@Import(OtherConfig.class)`)。 - 处理`@ComponentScan`:扫描指定包路径下的组件,进一步识别其他配置类或`@Component`类。 例如,以下配置类会被解析并生成两个`BeanDefinition`(`dataSource`和`jdbcTemplate`): ```java @Configuration public class AppConfig { @Bean public DataSource dataSource() { ... } @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource()); // 此处调用会被代理拦截 } } ``` #### 2. Bean定义的生成与注册 每个`@Bean`方法会被转换为一个`BeanDefinition`,并通过`BeanDefinitionRegistry`注册到Spring容器。**关键点**在于: - 配置类本身也会被注册为一个Bean(如`appConfig`),以便支持方法间的依赖调用。 - 若配置类被`@Import`引入,其`@Bean`方法会合并到主配置类中。 #### 3. CGLIB代理机制(核心增强) `@Configuration`类默认会通过**CGLIB动态代理**增强,确保`@Bean`方法的单例性。代理机制的核心逻辑如下: 1. **代理类生成**:Spring通过`Enhancer`生成配置类的子类,覆盖所有`@Bean`方法。 2. **方法拦截**:当调用`@Bean`方法时,代理会先检查容器中是否已存在该Bean: - 若存在,直接返回容器中的实例; - 若不存在,调用父类方法创建实例并注册到容器。 例如,上述`jdbcTemplate()`方法调用`dataSource()`时,实际调用的是代理类的方法,而非直接创建新对象。这避免了因多次调用`dataSource()`导致多个实例的问题[^3]。 #### 4. 与其他机制的交互 - **条件化加载**:`@Profile`或`@Conditional`注解会在解析阶段生效,决定是否注册特定Bean。 - **处理阶段**:配置类的解析和增强发生在`BeanFactoryPostProcessor`阶段(由`ConfigurationClassPostProcessor`驱动)[^3]。 #### 与`@Component`的区别 若将`@Configuration`替换为`@Component`,`@Bean`方法之间的直接调用**不会经过代理**,可能导致单例失效。例如: ```java @Component public class AppConfig { @Bean public DataSource dataSource() { ... } @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource()); // 每次调用都会创建新的DataSource实例! } } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值