谈起SpringBoot,面试官必问:讲述一下 SpringBoot 自动装配原理?

SpringBoot自动配置原理是什么?

面试过程中问得最多的可能是自动装配的原理,而自动装配是在启动过程中完成,只不过在刚开始的时候我们选择性的跳过了,下面详细讲解自动装配的过程。

1、在springboot的启动过程中,有一个步骤是创建上下文,如果不记得可以看下面的代码:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            //此处完成自动装配的过程
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

2、在prepareContext方法中查找load方法,一层一层向内点击,找到最终的load方法

//prepareContext方法
    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //load方法完成该功能
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }


    /**
     * Load beans into the application context.
     * @param context the context to load beans into
     * @param sources the sources to load
     * 加载bean对象到context中
     */
    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        //获取bean对象定义的加载器
        BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        loader.load();
    }

    /**
     * Load the sources into the reader.
     * @return the number of loaded beans
     */
    int load() {
        int count = 0;
        for (Object source : this.sources) {
            count += load(source);
        }
        return count;
    }

3、实际执行load的是BeanDefinitionLoader中的load方法,如下:

    //实际记载bean的方法
    private int load(Object source) {
        Assert.notNull(source, "Source must not be null");
        //如果是class类型,启用注解类型
        if (source instanceof Class<?>) {
            return load((Class<?>) source);
        }
        //如果是resource类型,启动xml解析
        if (source instanceof Resource) {
            return load((Resource) source);
        }
        //如果是package类型,启用扫描包,例如@ComponentScan
        if (source instanceof Package) {
            return load((Package) source);
        }
        //如果是字符串类型,直接加载
        if (source instanceof CharSequence) {
            return load((CharSequence) source);
        }
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }

4、下面方法将用来判断是否资源的类型,是使用groovy加载还是使用注解的方式

    private int load(Class<?> source) {
        //判断使用groovy脚本
        if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
            // Any GroovyLoaders added in beans{} DSL can contribute beans here
            GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
            load(loader);
        }
        //使用注解加载
        if (isComponent(source)) {
            this.annotatedReader.register(source);
            return 1;
        }
        return 0;
    }

5、下面方法判断启动类中是否包含@Component注解,但是会神奇的发现我们的启动类中并没有该注解,继续更进发现MergedAnnotations类传入了一个参数
SearchStrategy.TYPE_HIERARCHY,会查找继承关系中是否包含这个注解,@SpringBootApplication-->@SpringBootConfiguration-->@Configuration-->@Component,当找到@Component注解之后,会把该对象注册到AnnotatedBeanDefinitionReader对象中

private boolean isComponent(Class<?> type) {
   // This has to be a bit of a guess. The only way to be sure that this type is
   // eligible is to make a bean definition out of it and try to instantiate it.
   if (MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class)) {
      return true;
   }
   // Nested anonymous classes are not eligible for registration, nor are groovy
   //
<think>嗯,用户现在的是Spring Boot自动装配原理,这应该是在准备面试吧。我之前已经回答过SQL优化的题,现在用户可能是在继续准备技术面试,特别是Spring Boot相关的。我需要确保解释清楚自动装配的核心机制,因为这是Spring Boot的重要特性之一。 首先,我得回忆一下Spring Boot自动装配的关键点。记得自动装配主要依赖于@EnableAutoConfiguration和spring.factories文件,还有条件注解比如@ConditionalOnClass。用户可能需要一个结构清晰的回答,分步骤说明原理,可能还要对比Spring的传统配置,突出自动装配的优势。 接下来,用户可能想知道自动装配的具体流程,比如启动类上的@SpringBootApplication注解,它组合了@EnableAutoConfiguration。然后,Spring Boot会加载META-INF/spring.factories中的配置类,这些配置类使用条件注解来决定是否生效。需要举例子,比如DataSourceAutoConfiguration,这样更具体。 另外,用户可能对自动装配的底层机制不太清楚,比如如何扫描和加载配置类,条件注解的作用,以及如何排除不要的自动配置。所以需要详细解释这些部分,可能还要提到@Import和AutoConfigurationImportSelector的作用,以及如何通过配置开启或关闭自动装配。 还要注意回答的结构,按照步骤分点,结合代码示例和注释,帮助用户理解。比如用代码块展示@SpringBootApplication的组成,或者spring.factories的内容。同时,要避免使用过于专业的术语,保持解释的易懂性。 最后,总结自动装配的意义,比如简化配置、约定优于配置、模块化设计,这样用户能理解为什么自动装配重要。可能还需要提到常见的扩展点,比如自定义starter,或者如何排除自动配置,这样用户在面试中遇到相关题也能应对。 检查一下有没有遗漏的关键点,比如条件注解的具体类型,或者自动装配的触发过程。确保覆盖到核心概念,并且用简洁明了的语言表达出来,这样用户能快速掌握重点,应对面试官的提。</think>以下是Spring Boot自动装配原理的逐步解析,按核心流程梳理: --- ### 一、核心机制总述 **核心目标**:自动加载符合条件的Bean到IOC容器,**无需手动编写XML或Java配置** **实现原理** = `@EnableAutoConfiguration` + `spring.factories` + **条件注解** --- ### 二、实现步骤拆解 #### 1. 入口注解:@SpringBootApplication ```java @SpringBootApplication // 复合注解 public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } } ``` - 分解组成: ```java @SpringBootConfiguration // 标记为配置类 @EnableAutoConfiguration // 激活自动装配的核心注解 @ComponentScan(...) // 组件扫描 ``` --- #### 2. @EnableAutoConfiguration 的触发 - 通过`@Import`引入`AutoConfigurationImportSelector` ```java @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... } ``` - `AutoConfigurationImportSelector` 关键方法: ```java // 获取所有自动配置类 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); ``` --- #### 3. 加载自动配置类(spring.factories机制) - **扫描路径**:`META-INF/spring.factories` - **配置示例**(spring-boot-autoconfigure包中的文件): ```properties # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ ... ``` --- #### 4. 条件注解过滤 配置类通过**条件注解**动态判断是否生效: - `@ConditionalOnClass`:类路径存在指定类时生效 ```java @ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) public class DataSourceAutoConfiguration { ... } ``` - 其他常用条件注解: - `@ConditionalOnMissingBean`:容器中没有指定Bean时生效 - `@ConditionalOnProperty`:配置参数满足条件时生效 - `@ConditionalOnWebApplication`:Web环境生效 --- ### 三、自动装配流程图示 ``` 启动类 ↓ @SpringBootApplication → @EnableAutoConfiguration ↓ AutoConfigurationImportSelector ↓ 加载META-INF/spring.factories中的配置类 ↓ 通过条件注解筛选有效配置类 ↓ 注册符合条件的Bean到IOC容器 ``` --- ### 四、关键验证方法 1. **查看生效的自动配置类** 在`application.properties`中添加: ```properties debug=true ``` 启动时会打印: ``` Positive matches: // 已启用的自动配置 Negative matches: // 未启用的自动配置 ``` --- ### 五、自动装配的扩展与定制 1. **排除指定自动配置类** ```java @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) ``` 2. **自定义Starter** - 创建`XXX-spring-boot-starter`模块 - 编写配置类并注册到`spring.factories` - 使用条件注解控制加载条件 --- ### 六、与传统Spring配置的对比 | 维度 | Spring传统模式 | Spring Boot自动装配 | |--------------------|------------------------------|----------------------------------| | 配置方式 | XML/Java显式声明Bean | 自动发现+条件装配 | | 依赖管理 | 手动管理依赖版本 | starter统一管理依赖 | | 环境适配 | 需手动切换Profile配置 | 自动根据classpath环境加载配置 | | 开发效率 | 配置繁琐 | 开箱即用 | --- ### 七、典型面试点 1. **如何覆盖自动装配的默认配置?** - 自定义Bean(同名Bean优先级高于自动配置) 2. **自动装配与@ComponentScan的关系?** - `@ComponentScan`扫描当前包及子包的Bean - 自动装配加载第三方库的Bean(通过spring.factories) 3. **为什么引入starter依赖就能直接使用功能?** - starter中已包含`spring.factories`和条件配置类 --- ### 八、核心价值总结 1. **简化配置**:消除样板代码(如手动配置DataSource) 2. **约定优于配置**:通过默认配置快速启动项目 3. **模块化设计**:通过starter实现功能模块化装配
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值