Spring Boot自动装配

Spring Boot 的自动装配(Auto-configuration)是 Spring Boot 的核心特性之一,它使得开发者无需手动配置大量的 Spring 组件,而是通过在 classpath 中寻找 Spring Boot Starter 模块和条件化配置,自动完成配置和组装。这样,开发者就可以将精力集中在业务逻辑上,而不是在繁琐的配置上。

1.启动类:main方法

这是一个Spring Boot项目中的启动类,自动装配也要从这开始

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

我们重点关注这个注解run方法
@SpringBootApplication
SpringApplication.run(Application.class, args);

2.核心注解

2.1@SpringBootApplication

源码就不贴上来了
@SpringBootApplication相当于 @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan

2.2@SpringBootConfiguration

@SpringBootConfiguration 是继承自Spring的 @Configuration 注解,@SpringBootConfiguration 作用相当于 @Configuration
spring 3.0中增加了@Configuration,@Bean。可基于JavaConfig形式对 Spring 容器中的bean进行更直观的配置。SpringBoot推荐使用基于JavaConfig的配置形式。
在spring中基于xml方式配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-lazy-init="true">
    <bean id="mockService" class="..MockServiceImpl">
    ...
    </bean>
</beans>

引入@Configuration后,基于JavaConfig方式

@Configuration
public class MockConfiguration{
    @Bean
    public MockService mockService(){
        return new MockServiceImpl();
    }
}

总结,@Configuration相当于一个spring的xml文件,配合@Bean注解,可以在里面配置需要Spring容器管理的bean。

2.3@ComponentScan

基于xml方式配置包扫描路径:

<context:component-scan base-package="com.youzan" use-default-filters="false">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

基于**@ComponentScan注解** + @Configuration注解的方式:

@Configuration
@ComponentScan(value = "com.youzan", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class ScanConfig {
    
}

总结:@ComponentScan通常与@Configuration一起配合使用,相当于xml里面的context:component-scan,用来告诉Spring需要扫描哪些包或类。如果不设值的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,所以对于一个Spring Boot项目,一般会把入口类放在顶层目录中,这样就能够保证源码目录下的所有类都能够被扫描到。

2.4@EnableAutoConfiguration

@EnableAutoConfiguration完成了一下功能:
从classpath中搜寻所有的 META-INF/spring.factories 配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
@EnableAutoConfiguration 主要实现了 Spring Boot 自动装配的功能。在 Spring Boot 应用程序中,很多功能都可以通过添加对应的** Starter 依赖**来自动装配,而不需要显式地配置。这些 Starter 依赖包含了各种 Spring Boot 开箱即用的功能,例如数据库访问、Web 开发、缓存、安全、日志等等。Starter 依赖中都包含了一个或多个 spring.factories 配置文件,其中定义了要自动装配的类,这些类会在 Spring Boot 应用程序启动时被自动发现并注入到 Spring 容器中,从而实现自动装配的功能。@EnableAutoConfiguration 注解会在 Spring Boot 应用程序启动时自动扫描 classpath 中所有的 spring.factories 配置文件,并将其中定义的需要自动装配的类自动注入到 Spring 容器中。

2.5@Import

相当于xml里面的,允许导入 Configuration注解类 、ImportSelector 和 ImportBeanDefinitionRegistrar的实现类,以及普通的Component类。

2.6@Conditional

Spring Boot的强大之处在于使用了 Spring 4 框架的新特性:@Conditional注释,此注解使得只有在特定条件满足时才启用一些配置。这也 Spring Boot “智能” 的关键注解。Conditional大家族如下:

  • @ConditionalOnBean
  • @ConditionalOnClass
  • @ConditionalOnExpression
  • @ConditionalOnMissingBean
  • @ConditionalOnMissingClass
  • @ConditionalOnNotWebApplication
  • @ConditionalOnResource
  • @ConditionalOnWebApplication

上面所有的注解都在做一件事:注册bean到spring容器。他们通过不同的条件不同的方式来完成:

  • @SpringBootConfiguration 通过与 @Bean 结合完成Bean的 JavaConfig配置;
  • @ComponentScan 通过范围扫描的方式,扫描特定注解注释的类,将其注册到Spring容器;
  • @EnableAutoConfiguration 通过 spring.factories 的配置,并结合 @Condition 条件,完成bean的注册;
  • @Import 通过导入的方式,将指定的class注册解析到Spring容器;

3.Spring Boot启动流程

再分析完了重要的注解后,分析这行代码:
SpringApplication.run(Application.class, args);
Ctrl + B进入run方法源码,

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    //这里又调用了一个run方法
    return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    //这里分为new SpringApplication()和run方法
    return (new SpringApplication(primarySources)).run(args);
}

3.1 SpringApplication的实例化

new SpringApplication()

//构造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        ......
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    // 判断是否为web环境
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 设置初始化器
    this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置监听器
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    // 推断应用入口类
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

3.1.1 判断是否为web环境

这里通过判断是否存在 Servlet 和 ConfigurableWebApplicationContext 类来判断是否是Web环境,上文提到的 @Conditional 注解也有基于 class 来判断环境, 所以在 Spring Boot 项目中 jar包 的引用不应该随意,不需要的依赖最好去掉。

3.1.2 设置初始化器

这个方法会尝试从类路径的 META-INF/spring.factories 读取相应配置文件,然后进行遍历,读取配置文件中Key为org.springframework.context.ApplicationContextInitializer 的 value。
将读取到得类名放入集合中,准备实例化。

3.1.3 设置监听器

类似于设置初始化器

3.1.4 推断应用入口类

它通过构造一个运行时异常,通过异常栈中方法名为main的栈帧来得到入口类的名字。

private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
        }

        return null;
    }

3.2 SpringApplication.run方法

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    // 1、获取SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    // 通知监听者,开始启动
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //2、根据SpringApplicationRunListeners以及参数来准备环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        //3、创建Spring上下文
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        //4、Spring上下文前置处理
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        //5、Spring上下文刷新
        this.refreshContext(context);
        //6、Spring上下文后置处理
        this.afterRefresh(context, applicationArguments);
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
        }

        listeners.started(context, timeTakenToStartup);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var12) {
        this.handleRunFailure(context, var12, listeners);
        throw new IllegalStateException(var12);
    }

    try {
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
        return context;
    } catch (Throwable var11) {
        this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var11);
    }
}

3.2.1 获取RunListeners

private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
    }

利用了 getSpringFactoriesInstances 方法来获取实例,所以这里还是从 META-INF/spring.factories 中读取Key为org.springframework.boot.SpringApplicationRunListener 的Values

3.2.2 准备Environment环境

监听器会监听一些准备好的环境事件

3.2.3 创建Spring Context

思考:ssm项目中有几个上下文环境,Spring Boot中有几个上下文环境,为什么?
在SSM(Spring+Spring MVC+MyBatis)项目中,通常有两个上下文环境:Spring上下文和MyBatis上下文。
Spring上下文是整个Spring应用程序的核心容器,它管理应用程序中的所有Bean。Spring上下文负责加载Bean定义、创建Bean实例、管理Bean的生命周期以及处理依赖注入等任务。在SSM项目中,Spring上下文主要用于管理Service、Controller等组件。
MyBatis上下文是MyBatis框架中的一个组件,它管理MyBatis的SqlSessionFactory、SqlSession等实例。MyBatis上下文负责加载MyBatis配置文件、创建SqlSessionFactory实例、管理SqlSession实例以及处理数据库连接等任务。在SSM项目中,MyBatis上下文主要用于管理Mapper接口和Mapper XML文件的映射关系。
在Spring Boot中,通常有三个上下文环境:Spring应用上下文、Spring Boot上下文和Web上下文。
Spring应用上下文和SSM中的Spring上下文类似,是Spring Boot应用程序的核心容器,管理所有的Bean实例。
Spring Boot上下文是Spring Boot框架中的一个组件,它提供了许多Spring Boot特有的功能,如自动配置、属性绑定等。Spring Boot上下文负责加载Spring Boot的配置文件、创建自动配置实例、管理ApplicationContext实例等任务。
Web上下文是Spring Boot中专门为Web应用程序设计的上下文,它扩展了Spring应用上下文,增加了与Web相关的功能,如处理HTTP请求和响应、管理Servlet容器等。
在Spring Boot中存在多个上下文环境的原因是为了实现组件的分离和解耦。不同的上下文环境负责不同的任务,使得应用程序的各个部分能够协同工作,同时也方便了开发人员对应用程序进行配置和扩展。

3.2.4 Spring Context前置处理

3.2.5 Spring Context刷新

3.2.6 Spring Context后置处理

在Spring应用程序中,Spring上下文的创建和管理过程通常包括三个阶段:前置处理、上下文刷新和后置处理。
前置处理(Pre-processing):在创建Spring上下文之前,Spring会执行一些前置处理操作,例如解析XML或注解配置文件、加载Bean定义、初始化各种环境和资源等。这些操作都是为了为后续的上下文刷新做好准备。
上下文刷新(Context refresh):在上下文刷新阶段,Spring会执行各种初始化操作,包括实例化和初始化所有的Bean、解决Bean之间的依赖关系、注册Bean后置处理器等。这个阶段通常是Spring应用程序的核心过程。
后置处理(Post-processing):在上下文刷新完成后,Spring会执行一些后置处理操作,例如初始化Spring AOP、注册ServletContext等。这些操作可以根据需要进行扩展和定制,以满足应用程序的需求。
总的来说,前置处理、上下文刷新和后置处理三个阶段组成了Spring上下文的完整生命周期。在这个生命周期中,Spring会自动执行一系列的操作,使得应用程序的各个组件能够协同工作,同时也方便了开发人员对应用程序进行配置和扩展。

参考文章:Spring Boot自动装配原理与启动过程详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小C卷Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值