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 示例解析等内容。把握了这些根底的组成内容及其功用,在集成其他三方类库的主动装备时,才可以更加清晰地了解它们都运用了主动装备的哪些功用。
评论(0)