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会自动执行一系列的操作,使得应用程序的各个组件能够协同工作,同时也方便了开发人员对应用程序进行配置和扩展。