1、前言
SpringBoot 最中心的功用便是主动安装,Starter 作为 SpringBoot 的中心功用之一,依据主动装备代码供给了主动装备模块及依靠的才能,让软件集成变得简略、易用。运用 SpringBoot 时,咱们只需引人对应的 Starter,SpringBoot 发动时便会主动加载相关依靠,集成相关功用,这便是 SpringBoot 的主动安装功用。
简略概括其主动装备的原理:由@SpringBootAppliction
组合注解中的@EnableAutoConfiguration
注解敞开主动装备,加载 spring.factories 文件中注册的各种 AutoConfiguration 装备类,当其 @Conditional 条件注解收效时,实例化该装备类中界说的 Bean,并注入 Spring 上下文。
SpringBoot 主动安装进程触及以下主要内容:
@EnableAutoConfiguration: 扫描类途径下的META-INF/spring.factories
文件,并加载其间中注册的 AutoConfiguration 装备类,敞开主动安装;
spring.factories:装备文件,位于 jar 包的 META-INF 目录下,依照指定格式注册了 AutoConfiguration 装备类;
AutoConfiguration 类:主动装备类,SpringBoot 的许多以 xxxAutoConfiguration 命名的主动装备类,界说了三方组件集成 Spring 所需初始化的 Bean 和条件;
@Conditional 条件注解及其行生注解:运用在 AutoConfiguration 类上,设置了装备类的实例化条件;
Starter:三方组件的依靠及装备,包括 SpringBoot 预置组件和自界说组件,往往会包括 spring.factories 文件、AutoConfiguration 类和其他装备类。
其功用间的效果联系如下图:
2、@SpringBootApplication 注解
SpringBoot 项目创立完结会默认生成一个xxxApplication
的进口类,经过该类的 main 办法即可发动 SpringBoot 项目,经过调用 SpringApplication 的静态办法 run 作为 SpringBoot 项目的进口,代码如下:
@SpringBootApplication
public class LearnSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(LearnSpringBootApplication.class, args);
}
}
关于 SpringBoot Jar 的发动,可以看下我的这篇博客:SpringBoot JAR 是怎么运行的
在 SpringBoot 进口类上,仅有的注解便是 @SpringBootApplication。它是 SpringBoot 项目的中心注解,用于敞开主动装备,更精确地说,是其组合的@EnableAutoConfiguration
敞开了主动安装功用,@SpringBootApplication
的部分源码如下:
@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 {};
// 指定扫描的包名,激活包下 @Component 等注解组件的初始化
@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 办法以强制执行 bean 的生命周期行为
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@SpringBootApplication
注解中组合了@SpringBootConfiguration
、@EnableAutoConfiguration
和@ComponentScan
,其间@SpringBootConfiguration
便是@Configuration
,在实践进程中也可以运用这3个注解来替代@SpringBootApplication
。
上面说到@EnableAutoConfiguration
用来敞开主动安装功用,接下来要点看下该注解。
3、了解 @EnableAutoConfiguration
@EnableAutoConfiguration
的主要功用是在发动 Spring 应用程序上下文时进行主动装备,它会测验装备项目或许需求的 Bean。主动装备通常是依据项目 classpath 中引进的类和已界说的 Bean 来完结的。在此进程中,被主动装备的组件来自项目本身和项目依靠的 jar 包中。
@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 {};
}
上文说到,在 SpringBoot 发动应用上下文的时分,@EnableAutoConfiguration
会测验装备项目或许需求的 Bean,但在实践中假如不需求自定装备相关的 Bean,则可以经过exclude/excludeName
让主动化装备类失效,比方不期望数据源(DataSource)主动装备:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class LearnSpringBootApplication {
}
别的,留意注解类上的@AutoConfigurationPackage
,经过阅览其 JavaDoc 可知:当basePackages
或者basePackageClasses
没有指定的话, 被@EnableAutoConfiguration
注解的装备类所在的包便是扫描的根底包,这也是为什么@SpringBootApplication
的主装备类要放在顶级 package 下。
4、了解 AutoConfigurationImportSelector
@EnableAutoConfiguration
的主动装备功用是经过@Import
注解导入的ImportSelector
来完结的。 @Import(AutoConfigurationlmportSelector.class)
是@EnableAutoConfiguration
注解的组成部分,也是主动装备功用的中心完结者。下面讲解@Import
的基本运用办法和ImportSelector
的完结类AutoConfigurationlmportSelector
。
4.1 @Import 注解
@Import
注解,供给了导入装备类的功用。SpringBoot 的源代码中,有许多的EnableXXX
类都运用了该注解,了解@Import
有助于咱们了解 SpringBoot 的主动安装,@Import
有以下三个用处:
- 经过
@Import
引进@Configuration
注解的类; - 导入完结了
ImportSelector
或ImportBeanDefinitionRegistrar
的类; - 经过
@lmport
导入普通的POJO
。
4.2 AutoConfigurationlmportSelector 完结类
@Import
的许多功用都需求借助接口ImportSelector
来完结,ImportSelector
决定可引人哪些 @Configuration
的注解类,ImportSelector
接口源码如下:
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
ImportSelector
接口只供给了一个参数为AnnotationMetadata
的办法,回来的成果为一个字符串数组。其间参数AnnotationMetadata
内包括了被@Import
注解的类的注解信息。在selectimports
办法内可依据详细完结决定回来哪些装备类的全限定名,将成果以字符串数组的方式回来。
假如完结了接口ImportSelector
的类的一起又完结了以下4个Aware
接口,那么 Spring 确保在调用ImportSelector
之前会先调用Aware
接口的办法。这4个接口为:EnvironmentAware
、BeanFactoryAware
、 BeanClassLoaderAware
和ResourceLoaderAware
。
在AutoConfigurationlmportSelector
的源代码中就完结了这4个接口:
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
AutoConfigurationlmportSelector
完结了ImportSelector
的子接口 DeferredlmportSelector
。DeferredlmportSelector
接口与ImportSelector
的区别是,前者会在一切的 @Configuration 类加载完结之后再加载回来的装备类,而ImportSelector
是在加载完 @Configuration 类之前先去加载回来的装备类。
其加载流程如图所示:
AutoConfigurationImportSelector
完结的selectImports
办法如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 主动装备开关
// 查看主动装备是否敞开是经过读取环境变量 spring.boot.enableautoconfiguration 确定的,默以为 true
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 回来封装后的主动装备信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
// 回来契合条件的装备类
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 过 SpringFactoriesLoader 类供给的办法加载类途径中 META-INF 目录下的 spring.factories 文件中针对 EnableautoConfiguration 的注册装备类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 对取得的注册装备类调集进行去重处理,避免多个项目引进相同的装备类
configurations = removeDuplicates(configurations);
// 取得注解中被 exclude 或 excludeName 所扫除的类的调集
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 查看被扫除类是否可实例化,是否被主动注册装备所运用,不契合条件则抛出异常
checkExcludedClasses(configurations, exclusions);
// 从主动装备类调集中去除被扫除的类
configurations.removeAll(exclusions);
// 查看装备类的注解是否契合 spring.factories 文件中 AutoConfigurationImportFilter 指定的注解查看条件
configurations = getConfigurationClassFilter().filter(configurations);
// 将饰选完结的装备类和排查的装备类构建为事件类,并传入监听器。监听器的装备在于 spring.factories 文件中,经过 AutoConfigurationImportlistener 指定
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
getAutoConfigurationEntry()
办法获取装备类的进程比较复杂,触及到装备类的查看、去重、监听,就不逐个展开了。要害点在于getCandidateConfigurations
办法中运用SpringFactoriesLoader
加载META-INF/spring.factories
文件中装备的EnableAutoConfiguration
类型的主动装备类:
List<String> configurations =
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
看下META-INF/spring.factories
文件的部分内容:
SpringFactoriesLoader 加载了这些xxxAutoConfiguration
装备类,经过上面说到的加载、过滤、实例化的进程后,就主动敞开某一些详细的功用。
4.3 @Conditional 条件注解
前面咱们看了AutoConfigurationlmportSelector
是怎么进行主动装备类的读取和筛选的,翻开每一个主动装备类,咱们都会看到@Conditional
或其行生的条件注解,其效果是限定主动装备类的收效条件,下面讲一下@Conditional
注解原理。@Conditional
可依据是否满意指定的条件来决定是否进行 Bean 的实例化及安装(比方,设定当类途径下包括某个类的时分才会对注解的类进行实例化操作),便是依据一些特定条件来控制 Bean 实例化的行为,@Conditional
代码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
@Conditional 注解仅有的元素特点是接口 Condition 的数组,只有在数组中指定的一切Condition
的matehes
办法都回来true
的情况下,被注解的类才会被加载:
@FunctionalInterface
public interface Condition {
// 决定条件是否匹配
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
matches
办法的第一个参数为ConditionContext
,可经过该接口供给的办法来取得 Spring 应用的上下文信息;matches
办法的第二个参数为AnnotatedTypeMetadata
,该接口供给了访问特定类或办法的注解功用,可以用来查看带有@Bean
注解的办法上是否还有其他注解。
4.4 @Conditional 的衍生注解
SpringBoot 供给了许多依据@Conditional
注解的衍生注解,它们适用不同的场景并供给了不同的功用,这些注解均位于spring-boot-autoconfigure
项目的org.springframework.boot.autoconfigure.condition
包下,比方:
@ConditionalOnBean:在容器中有指定 Bean 的条件下;
@ConditionalOnClass: 在 classpath 类途径下有指定类的条件下;
@ConditionalOnMissingBean: 当容器里没有指定 Bean 的条件时;
@ConditionalOnMissingClass:当类途径下没有指定类的条件时;
@ConditionalOnProperty:在指定的特点有指定值的条件下;
@ConditionalOnWebApplication:在项目是一个 Web 项目的条件下;
阅览以上这些注解的源码可以看到,它们都组合了@Conditional
注解,经过不同的Condition
完结类完结不同的功用,并且用到的完结类大部分都承继自完结了Condition
接口的SpringBootCondition
笼统类,其承继联系如下:
以ConditionalOnClass
为例,看下详细代码:
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
...
}
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
...
}
public abstract class SpringBootCondition implements Condition {
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
// 调用新界说的笼统办法 getMatchOutcome 并交由子类来完结,
// 在 matches 办法中依据子类 getMatchOutcome 回来的成果判别是否匹配
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {}
}
// 在笼统类 SpringBootCondition 中完结了 matches 办法,是经过调用新界说的笼统办法 getMatchOutcome 并交由子类来完结
// 在 matches 办法中依据子类回来的成果判别是否匹配
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
}
5、SpringBoot starter 实践
上文现已讲解了 SpringBoot 的中心运作原理,SpringBoot 运用上述主动装备原理完结了许多的starter
,可以在项目中快速集成。日常工作中咱们可以借鉴 SpringBoot 的 starter 的创立机制来创立自己的starter
,这一节咱们自己完结一个简略的starter
项目,可以快速集成到其他 SpringBoot 项目中。
咱们规划一个能判别当时环境的starter
项目:引进该项目后,会依据环境在 Spring 上下文中主动注入一个对应当时环境的完结IProfileService
接口的类Bean
,该Bean
的getProfile
办法可以回来当时的环境名,咱们需求依据不同环境注入不同的Bean
:
// 不同环境有不同的完结类:DevService、ProdService
public interface IProfileService {
String getProfile();
}
首要创立一个Maven
项目,命名为spring-boot-starter-profile
,spring-boot-starter
项目依靠spring-boot-autoconfigure
,引进其Maven
坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
创立com.spring.learn.IProfileService
接口和对应dev
和prod
环境的两个完结类:
public interface IProfileService {
String getProfile();
}
// dev 环境下的完结类
public class DevService implements IProfileService{
@Override
public String getProfile() {
return "dev";
}
}
// prod 环境下的完结类
public class ProdService implements IProfileService {
@Override
public String getProfile() {
return "prod";
}
}
接下来,创立主动装备类 ProfileAutoConfiguration,怎么判别环境?咱们用上文说到的@ConditionalOnProperty 替代,经过在 properties(或 yml)装备文件中装备profile.service.enabled
装备项来模仿不同环境:
@Configuration
@ConditionalOnMissingBean(IProfileService.class) // 容器中不存在 IProfileService 类型的 Bean 时才收效
public class ProfileAutoConfiguration {
@Bean
// profile.service.enabled=dev 时,注入 DevService
@ConditionalOnProperty(prefix = "profile.service", value = "enabled", havingValue = "dev")
public DevService devService() {
return new DevService();
}
@Bean
// profile.service.enabled=prod 时,注入 ProdService
@ConditionalOnProperty(prefix = "profile.service", value = "enabled", havingValue = "prod")
public ProdService prodService() {
return new ProdService();
}
}
在resources
目录下创立META-INF/spring.factories
文件,并填入以下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.spring.learn.ProfileAutoConfiguration
最终,经过mvn clean install
将该包安装到本地仓库,方便其他项目引证。
恣意翻开一个 SpringBoot 项目,引进咱们的自界说starter
坐标:
<dependency>
<groupId>org.spring.learn</groupId>
<artifactId>spring-boot-starter-profile</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
在application.properties
装备文件中指定下环境装备,模仿不同环境:
profile.service.enabled=prod
经过SpringApplicationContext
获取IProfileService
类型Bean
,打印getProfile
办法获取的环境称号:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(LearnSpringBootApplication.class, args);
IProfileService bean = context.getBean(IProfileService.class);
System.out.println("get profile from IProfileService: " + bean.getProfile());
}
打印成果如下:
6、小结
本文环绕 SpringBoot 的中心功用展开,从总体上了解 SpringBoot 主动装备的原理以及主动装备中心组件的运作进程,要点学习主动装备原理、@EnableAutoConfiguration、 @lmport、 ImportSelector、@Conditional 以及 starter 示例解析等内容。把握了这些根底的组成内容及其功用,在集成其他三方类库的主动装备时,才可以更加清晰地了解它们都运用了主动装备的哪些功用。