Spring的执行流程
1. 加载容器(加载配置文件)
2. 根据配置完成Bean的初始化(扫描配置范围内的五大类注解)
3. 将被五大类注解修饰的类注册到Spring容器中
(将对象交给Spring IoC容器管理)
4.注入Bean对象(@Autowired、@Resource)
Spring Boot自动配置
Spring Boot的自动配置是Spring Boot框架提供的一项特性,它在Spring容器启动后会自动加载一些配置类和Bean对象到IoC容器中,无需手动声明,从而简化了开发流程,减少了繁琐的配置操作。
具体来说,Spring Boot的自动配置是通过扫描classpath下的各种配置类和标记了特定注解的类来实现的。这些配置类通常位于依赖jar包中,其中可能包含了一些默认的配置信息、Bean定义以及各种组件。Spring Boot会自动识别这些配置类,并根据特定的规则将它们加载到Spring IoC容器中。
在加载这些配置类时,Spring Boot会根据条件判断是否需要应用这些配置。例如,它会检查当前项目中的其他配置、环境变量、系统属性等,来决定是否应用某个配置类。这样一来,开发者可以根据自己的需求对自动配置进行定制,从而实现更灵活和个性化的配置。
总的来说,Spring Boot的自动配置使得开发者可以更加专注于业务逻辑的实现,而无需过多关注底层的配置细节,极大地提高了开发效率和代码质量。
我们了解主要分以下两个方面:
1. Spring 是如何把对象加载到SpringIoC容器中的
2. SpringBoot 是如何实现的
Spring 加载Bean
问题描述
需求:使用Spring管理第三方的jar包配置。
引入第三方的包,其实就是在该项目下,引⼊第三方的代码。我们采⽤在该项目下创建不同的目录来模拟第三方的代码引入。
数据准备:
第三方文件代码:
@Component
public class BiteConfig {
public void study(){
System.out.println("start study...");
}
}
@SpringBootTest
class SpringAutoconfigApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
void contextLoads() {
BiteConfig biteConfig = applicationContext.getBean(BiteConfig.class,
"biteConfig");
System.out.println(biteConfig);
}
}
原因分析
启动类所在⽬录为: com.example.demo , ⽽ BiteConfig 这个类在com.bite.autoconfig 下, 所以SpringBoot并没有扫描到.
解决方案
当我们引入第三方的 jar 包时,其代码目录通常不会与 Spring Boot 项目的启动类在同一个目录下。为了让 Spring 能够扫描到这些第三方 jar 包中的组件并加载到 Spring IoC 容器中,我们需要采取一些解决方案。常见的解决方案有两种:
- @ComponentScan 组件扫描: 在 Spring Boot 项目中,通常会有一个主启动类,该类上会使用 @SpringBootApplication 注解进行标注。这个注解会自动进行组件扫描并将其加载到 Spring IoC 容器中。除了主启动类之外,其他需要被 Spring 管理的组件也可以使用 @ComponentScan 注解或者其他方式指定扫描的包路径,以确保它们被 Spring 框架扫描到并加载到 IoC 容器中。
- @Import 导入: 使用 @Import 注解可以导入其他的配置类或者普通的 Java 类,被导入的类会被 Spring 加载到 IoC 容器中。通过在主配置类上使用 @Import 注解导入需要加载的类,可以让 Spring 加载这些类并管理它们。
@ComponentScan:
通过 @ComponentScan 注解,指定Spring扫描路径。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.bite.autoconfig")
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
可以指定扫描多个包@ComponentScan({"com.bite.autoconfig","com.example.demo"})
Spring 是否使⽤了这种⽅式呢?⾮常明显, 没有.(因为我们引⼊第三⽅框架时, 没有加扫描路径. ⽐如mybatis)如果Spring Boot采⽤这种⽅式, 当我们引⼊⼤量的第三⽅依赖, ⽐如Mybatis, jackson等时, 就需要在启动类上配置不同依赖需要扫描的包, 这种⽅式会⾮常繁琐.具体来说:
Spring Boot 采用了自动配置的方式来管理第三方依赖,而不是使用 @ComponentScan 这种方式。自动配置是通过在 Spring Boot 启动类上使用 @SpringBootApplication 注解来实现的。这个注解包含了多个注解的功能,其中包括了 @ComponentScan 注解。
当我们引入第三方依赖时,Spring Boot 会根据依赖的类路径自动检测并配置相应的组件。例如,当引入 MyBatis 依赖时,Spring Boot 会自动检测到 MyBatis 相关的配置类,并将其集成到应用程序中。
因此,我们无需手动配置扫描路径或使用 @ComponentScan 注解来扫描第三方依赖的包。这种自动配置的方式大大简化了开发流程,并使得应用程序的配置更加简洁和易于维护。
@Import
@Import 注解主要有以下几种形式:
导入类: 可以直接将一个或多个类导入到当前的配置类中。这些类可以是普通的配置类、普通的 Bean 类或者其他注解类。
@Import({MyConfiguration.class, MyBean.class})
导入 ImportSelector 接口实现类: ImportSelector 接口允许我们根据条件动态地选择要导入的配置类。通过实现 ImportSelector 接口,我们可以根据条件返回需要导入的配置类的全限定名数组。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根据条件动态选择要导入的配置类
if (someCondition) {
return new String[]{MyConfiguration1.class.getName()};
} else {
return new String[]{MyConfiguration2.class.getName()};
}
}
}
然后需要在配置类中使用 @Import 注解导入 ImportSelector 接口实现类。
@Import(MyImportSelector.class)
@Import(MyImportSelector.class)
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
依赖中有哪些Bean, 使⽤时需要配置哪些bean, 第三⽅依赖最清楚, 那能否由第三⽅依赖来做这件事呢?• ⽐较常⻅的⽅案就是第三⽅依赖给我们提供⼀个注解, 这个注解⼀般都以@EnableXxxx开头的注解, 注解中封装的就是 @Import 注解
第三⽅依赖提供注解
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导⼊哪些类
public @interface EnableBiteConfig {
}
注解中封装 @Import 注解, 导⼊ MyImportSelector.class
@EnableBiteConfig
@SpringBootApplication
public class SpringAutoconfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAutoconfigApplication.class, args);
}
}
SpringBoot原理分析
源码阅读
SpringBoot 是如何帮助我们做的呢? ⼀切的起源自SpringBoot的启动类:
定义一个注解 @SpringBootApplication,用于标识 Spring Boot 应用程序的主类。该注解包含了 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan 注解,并且提供了一些属性用于配置自动配置和组件扫描。
// 声明一个注解,用于标注 Spring Boot 应用的主类
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE}) // 该注解可以作用于类上
@Retention(RetentionPolicy.RUNTIME) // 注解信息保留到运行时
@Documented // 注解包含在 Javadoc 中
@Inherited // 允许子类继承父类的注解
@SpringBootConfiguration // 该注解标注当前类是 Spring Boot 的配置类
@EnableAutoConfiguration // 启用自动配置
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {}; // 排除自动配置的类
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {}; // 排除自动配置的类的名称
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {}; // 扫描的基础包路径
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {}; // 扫描的基础包路径下的类
@AliasFor(
annotation = ComponentScan.class,
attribute = "nameGenerator"
)
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; // 生成 Bean 名称的策略类,默认为 BeanNameGenerator
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true; // 是否启用代理 Bean 方法,默认为 true
}
@SpringBootApplication 是一个组合注解,注解中包含了:
元注解
元注解是一种特殊类型的注解,用于对其他注解进行注解。在Java开发工具包(JDK)中,有四个标准的元注解,也称为meta-annotation,分别是@Target、@Retention、@Documented和@Inherited。
- @Target:用于描述被修饰的注解可以应用的范围。这意味着它指定了注解可以放置在哪些元素上,例如类、方法、字段等。通过指定不同的ElementType参数,可以限制注解的使用范围,从而确保注解的正确使用。
- @Retention:用于描述注解被保留的时间范围。这决定了注解的生命周期,即它在什么时候会被丢弃。有三种保留策略:
- RetentionPolicy.SOURCE:编译器将会丢弃该注解,它不会包含在编译后的类文件中。
- RetentionPolicy.CLASS:注解将会被包含在编译后的类文件中,但在运行时不可获取。
- RetentionPolicy.RUNTIME:注解将被包含在类文件中,并且在运行时可以通过反射机制获取到。
- @Documented:用于描述是否在使用Java文档工具(如javadoc)为类生成文档时保留其注解信息。如果一个注解被@Documented修饰,那么在生成文档时,这个注解会被包含进去,使得开发者能够清晰地了解类的注解信息。
- @Inherited:使被它修饰的注解具有继承性。如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解。这意味着如果一个类被标注为某个注解,那么它的子类也会继承这个注解,除非子类显式地覆盖了这个注解。这对于定义一些通用的行为或特征并让子类继承这些行为或特征是非常有用的。
@SpringBootConfiguration
这段代码定义了一个自定义的注解@SpringBootConfiguration,它实际上是对@Configuration注解的封装,并添加了一些额外的功能。⾥⾯就是@Configuration, 标注当前类为配置类, 其实只是做了⼀层封装改了个名字⽽已.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@EnableAutoConfiguration (开启⾃动配置)
@ComponentScan (包扫描)
EnableAutoConfiguration 详解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
public class AutoConfigurationImportSelector implements
DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//获取⾃动配置的配置类信息
AutoConfigurationEntry autoConfigurationEntry =
this.getAutoConfigurationEntry(annotationMetadata);
return
StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
}
/**
* 根据提供的注解元数据获取自动配置条目。
*
* @param annotationMetadata 提供有关注解组件上的注解信息的注解元数据。
* @return AutoConfigurationEntry,表示自动配置条目
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 如果未启用自动配置,则返回一个空的自动配置条目
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 获取注解属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取候选配置类列表
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的配置类
configurations = this.removeDuplicates(configurations);
// 获取排除的配置类列表
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查排除的配置类是否存在重复
this.checkExcludedClasses(configurations, exclusions);
// 从候选配置类中移除排除的配置类
configurations.removeAll(exclusions);
// 应用配置类过滤器
configurations = this.getConfigurationClassFilter().filter(configurations);
// 触发自动配置导入事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回新的自动配置条目
return new AutoConfigurationEntry(configurations, exclusions);
}
}
//获取所有基于
//METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports⽂件
//META-INF/spring.factories⽂件中配置类的集合
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = new
ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderF
actoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class,
this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in
META-INF/spring.factories nor in METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
这⾥⾯包含了很多第三⽅依赖的配置⽂件
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
static class Registrar implements ImportBeanDefinitionRegistrar,
DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new
PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
/**
* 内部静态类Registrar实现了ImportBeanDefinitionRegistrar和DeterminableImports接口
* 用于注册Bean定义和确定导入项
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
/**
* 注册Bean定义的方法,将自动配置包注册到注册表中
* @param metadata 注解元数据
* @param registry Bean定义注册表
*/
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 使用PackageImports类获取包名,并将其注册到自动配置包中
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
/**
* 确定导入项的方法
* @param metadata 注解元数据
* @return 包含PackageImports类的集合
*/
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
- PackageImports(metadata):这行代码创建了一个PackageImports对象,该对象从传入的metadata中获取了与注解相关的信息,然后通过这些信息确定了需要注册到自动配置包的包名。
- new PackageImports(metadata).getPackageNames():这部分代码调用了PackageImports对象的getPackageNames()方法,以获取到需要注册到自动配置包中的包名集合。
- .toArray(new String[0]):将获取到的包名集合转换为数组。
- AutoConfigurationPackages.register(registry, ...):这一行代码调用了AutoConfigurationPackages类的register方法,该方法接受一个BeanDefinitionRegistry对象和一个包名数组作为参数。在Spring Boot内部,AutoConfigurationPackages类负责管理自动配置包,并将注册的包名添加到自动配置包中。这样一来,Spring在启动时就会扫描这些包,寻找带有特定注解的类,并将它们实例化为Bean。
综上所述,我们可以来总结一下:
当我们使用@EnableAutoConfiguration注解时,它实际上是启动了Spring Boot的自动配置功能,其实现原理涉及几个关键部分:
@Import({AutoConfigurationImportSelector.class}):
通过@Import注解,导入了实现了ImportSelector接口的AutoConfigurationImportSelector类。ImportSelector接口的实现类可以根据条件动态地选择需要导入的配置类。
AutoConfigurationImportSelector:
AutoConfigurationImportSelector类的selectImports()方法负责选择需要导入的配置类。底层调用了getAutoConfigurationEntry()方法,获取可自动配置的配置类信息集合。
getAutoConfigurationEntry()方法:
getAutoConfigurationEntry()方法通过调用getCandidateConfigurations()方法获取在配置文件中配置的所有自动配置类的集合。
getCandidateConfigurations()方法:
getCandidateConfigurations()方法获取所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件和META-INF/spring.factories文件中配置类的集合。这些配置文件通常包含了许多第三方依赖的自动配置类。
动态加载自动配置类
在加载自动配置类时,并不是将所有的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载。@Conditional是Spring底层注解,根据不同的条件进行不同的条件判断,如果满足指定的条件,配置类里边的配置才会生效。
@AutoConfigurationPackage注解:
@AutoConfigurationPackage注解将启动类所在的包下面所有的组件都扫描注册到Spring容器中。这样,Spring容器就能够扫描到启动类所在包及其子包中的所有组件,并将其注册为Spring Bean。
总的来说,@EnableAutoConfiguration注解启用了Spring Boot的自动配置功能,它通过导入AutoConfigurationImportSelector类和@AutoConfigurationPackage注解,动态加载自动配置类,并根据条件进行判断和加载,最终完成Spring应用程序的自动配置。
SpringBoot ⾃动配置原理的大概流程如下:
当Spring Boot程序启动时,会自动加载配置文件中所定义的配置类,并通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交由IOC容器管理。这样做的目的是为了简化Spring应用的配置和开发过程,让开发者专注于业务逻辑的实现而不必过多关注框架的配置。
具体来说,Spring Boot的自动配置原理可以概括如下:
- 启动过程:当Spring Boot应用启动时,会自动扫描classpath下的META-INF/spring.factories文件,该文件中列出了所有自动配置类的全限定名。
- 自动配置类:Spring Boot通过这些自动配置类来自动配置应用的各种组件,比如数据源、JPA、Web容器等等。这些自动配置类通过注解@Configuration标识,告诉Spring这是一个配置类。
- 条件装配:自动配置类中的各个Bean的创建是有条件的,Spring Boot利用条件注解(如@ConditionalOnClass、@ConditionalOnMissingBean等)来根据类路径、Bean是否存在等条件来决定是否创建某个Bean。
- 加载配置:Spring Boot会加载应用的配置文件(application.properties或application.yml),对一些默认配置值进行修改,并将这些配置信息注入到相应的Bean中
- IOC容器管理:最终,这些自动配置类中的Bean会被添加到Spring的IOC容器中进行管理。开发者可以通过@Autowired注解或者其他方式来获取并使用这些Bean。
总之,Spring Boot的自动配置机制大大简化了Spring应用的开发和部署流程,我们只需要遵循约定大于配置的原则,即可快速搭建出一个功能完善的Spring应用。