核心运行原理
我们通常在使用Spring Boot时,只需要引入对应的starters,Spring Boot启动时变回自动加载相关依赖,配置相应的初始化参数,以最快捷,简单的形式对第三方软件进行集成,这边是Spring Boot的自动配置功能。下图是Spring Boot实现该运作机制涉及的核心部分:
上图简单描述了Spring Boot自动配置功能运作过程中涉及的几个核心功能及其相互之间的关系包括@EnableAutoConfiguration,spring.factories,各组件对应的AutoConfiguration类,@Conditional注解以及各种Starters。
简单概括
Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖,配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。
核心注解解释
- @EnableConfiguration:该注解由组合注解@SpringBootApplication引入,完成自动配置开启,扫描各个jar包下的spring.factories文件,并加载文件中注册的AutoConfiguration类等。
- spring.factories:配置文件,位于jar包下的META-INF目录下,按照指定格式注册了自动配置的AutoConfiguration类。spring.factories也可以包含其他类型待注册的类。改配置文件不仅仅存在于Spring Boot项目中,也可以存在于自定义的自动配置(或starter)项目中。
- AutoConfiguration:自动配置类,代表了Spring Boot中一类以XXXAutoConfiguration命名的自动配置类。其中定义了三方组件集成Spring所需要的Bean和条件。
- Conditional:条件注解及其衍生注解,在AutoConfiguration类上使用,当满足该条件注解时才会实例化AutoConfiguration类。
- Starters:三方组件的依赖及配置,Spring Boot已经预置的组件。Spring Boot默认的Starters项目往往只包含了一个pom依赖的项目。如果是自定的starter,该项目还需要包含spring.factories文件,AutoConfiguration类和其他配置类。
运行原理源码解析之@EnableAutoConfiguration
@EnableAutoConfiguration是开启自动配置的注解,在创建Spring Boot项目看不到该注解,它是由组合注解@SpringBootApplication引入的。先来看下启动类和@SpringBootApplication注解的源码
启动类和@SpringBootApplication注解
/**
* @author lifly
* @description
* @date 2023-04-23 20:46
* @versoin 1.0.0
**/
@SpringBootApplication
public class SpringBootApplicationSource {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplicationSource.class,args);
}
}
相信你们在创建Spring Boot项目时也会有这样一个main方法,启动类的命名规范都是artifactId+Application.通过该启动类就可以启动Spring Boot项目了。这里只能看到一个注解@SpringBootApplication。它是Spring Boot项目的核心注解,包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan注解,它们用于开启自动配置,包扫描,加载配置类。@SpringBootApplication源码如下:
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@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 {
};
//指定是否代理@Bean方法以强制执行Bean的生命周期行为
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
- exclude:根据类(Class)排除指定的自动配置,该成员属性覆盖了@SpringBootApplication中组合的@EnableAutoConfiguration中定义的exclude成员属性。
- excludeName:根据类名排除指定的自动配置,覆盖了@EnableAutoConfiguration中的excludeName的成员属性。
- scanBasePackages:指定扫描的基础package,用于激活@Component等注解类的初始化。
- scanBasePackageClasses:扫描指定的类,用于组件的初始化。
- proxyBeanMethods:指定是否代理@Bean方法以强制执行bean的生命周期行为。此功能需要通过运行时生成CGLIB子类来实现方法拦截。该子类有一定的限制,比如配置类及其方法不允许声明为final等。proxyBeanMethods的默认值为true,允许配置类中进行inter-bean reference(bean之间的引用)以及对该配置的@Bean方法的外部调用。如果@Bean方法都是自包含的,并且提供了容器使用的普通工程方法的功能,则可设置为false,避免处理CGLIB子类。
通过以上代码我们发现,Spring Boot中大量使用了@AliasFor注解,该注解用于桥接dao其他注解,该注解的属性中指定了所桥接的注解类。如果点进去查看,会发现@SpringBootApplication定义的属性在其他注解中已经定义过了。之所以使用@AliasFor注解并重新在@SpringBootApplication中定义,更多是为了减少用户使用多注解带来的麻烦。
@SpringBootApplication注解中组合了@SpringBootConfiguration,@EnableAutoConfiguration和@ComponentScan。因此,在实践过程中也可以使用这3个注解来替代@SpringBootApplication。
以下是@SpringBootApplication注解组合结构图:
@EnableAutoConfiguration功能解析
在未使用Spring Boot的情况下,Bean的生命周期由Spring管理,然而Spring无法自动配置@Configuration注解的类。而Spring Boot的核心功能之一就是根据约定自动管理该注解标注的类。用来实现该功能的组件之一便是@EnableAutoConfiguration注解。@EnableAutoConfiguration位于spring-boot-autoconfigure包内,当使用@SpringBootApplication注解时,@EnableAutoConfiguration注解会自动生效。@EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常是基于项目classPath中引入的和已定义的Bean来实现的。在此过程中,被自动配置的组件来自项目自身和项目依赖的jar包中。
下面来看下@EnableAutoConfiguration注解的源码:
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({
AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
//用来覆盖配置开启、关闭自动配置的功能
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//根据类(class)排除指定的自动配置
Class?>[] exclude() default {
};
//根据类名排除指定的自动配置
String[] excludeName() default {
};
}
@EnableAutoConfiguration注解提供了一个常量和两个成员参数的定义
- ENABLED_OVERRIDE_PROPERTY :用来覆盖配置开启或关闭的自动配置的功能
- exclude:根据类(Class)排除指定的自动配置
- excludeName:根据类名排除指定的自动配置。
正如上文所说,@EnableAutoConfiguration会猜测你需要使用的Bean,但如果在实战中你并不需要它预配置的Bean,可以通过该注解的exclude或excludeName参数进行有针对性的排除,例如,不需要数据库的自动配置时,可以通过以下两种方法进行排除:
/**
* @author lifly
* @description
* @date 2023-04-23 20:46
* @versoin 1.0.0
**/
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringBootApplicationSource {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplicationSource.class,args);
}
}
/**
* @author lifly
* @description
* @date 2023-04-28 22:16
* @versoin 1.0.0
**/
@Configuration
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
public class TestConfiguration {
}
注意
被@EnableAutoConfiguration注解的类所在package还具有特定的意义,通常会被作为扫描注解@Entity的根路径。这也是在使用@SpringBootApplication注解时会被注解的类放在顶级package下的原因,如果放在较低级,它所在package的同级或上级中的类无法被扫描到。
AutoConfigurationImportSelector源码分析
@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。@Import(AutoConfigurationImportSelector.class)又可以分为两个部分:@Import和对应的ImportSelector。
@Import注解
@Import注解位于spring-context项目内,主要提供导入配置类的功能。@Import源码:
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class?>[] value();
}
@Import的作用和xml配置中”import/”标签的作用一样,我们可以通过@Import引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO(将其注册成Spring Bean,导入POJO需要Spring4.2以上版本)。
ImportSelector接口
@Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引入哪些@Configuration。ImportSelector接口源码如下:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
ImportSelector接口只提供了一个参数为AnnotationMetadata的方法,返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。
如果实现了接口ImportSelector的类的同时又实现了4个Aware接口,那么Spring保证在调用ImportSelector之前会先调用Aware接口的方法。这4个接口为EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware和ResourceLoaderAware。在AutoConfigurationImportSelector的源代码中就实现了这4个接口
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
在上面的源代码中,AutoConfigurationImportSelector并没有直接实现ImportSelector接口,而是实现了它的子接口DeferredImportSelector。DeferredImportSelector接口与ImportSelector的区别是,前者会在所有的@Configuration类加载完成之后再加载返回的配置类,而ImportSelector是在加载完@Configuration类之前加载返回的配置类。
DeferredImportSelector的加载顺序可以通过@Order注解或实现Ordered接口来指定。同时,DeferredImportSelector提供了新的方法getImportGroup()来跨DeferredImportSelector实现自定义Configuration的加载顺序。
AutoConfigutationImportSelector功能概述
下面通过一张图来从整体了解AutoConfigurationImportSelector的核心功能及流程,然后再对照源码看具体的功能实现。
当AutoConfigurationImportSelector被@Import注解引入之后,它的selectImports方法会被调用并执行其实现的自动装配逻辑。selectImports方法涵盖了组件自动装配的所有的处理逻辑。AutoConfigurationImportSelector的selectImports方法源码如下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
ListString> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
SetString> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations,