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 类和其他装备类。
其功用间的效果联系如下图:

了解SpringBoot的主动安装

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有以下三个用处:

  1. 经过@Import引进@Configuration注解的类;
  2. 导入完结了ImportSelectorImportBeanDefinitionRegistrar的类;
  3. 经过@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个接口为:EnvironmentAwareBeanFactoryAwareBeanClassLoaderAwareResourceLoaderAware

AutoConfigurationlmportSelector的源代码中就完结了这4个接口:

public class AutoConfigurationImportSelector
    implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}

AutoConfigurationlmportSelector完结了ImportSelector的子接口 DeferredlmportSelectorDeferredlmportSelector接口与ImportSelector的区别是,前者会在一切的 @Configuration 类加载完结之后再加载回来的装备类,而ImportSelector 是在加载完 @Configuration 类之前先去加载回来的装备类。
其加载流程如图所示:

了解SpringBoot的主动安装

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文件的部分内容:

了解SpringBoot的主动安装

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 的数组,只有在数组中指定的一切Conditionmatehes办法都回来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笼统类,其承继联系如下:

了解SpringBoot的主动安装

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,该BeangetProfile办法可以回来当时的环境名,咱们需求依据不同环境注入不同的Bean

// 不同环境有不同的完结类:DevService、ProdService
public interface IProfileService {
    String getProfile();
}

首要创立一个Maven项目,命名为spring-boot-starter-profilespring-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接口和对应devprod环境的两个完结类:

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());
}

打印成果如下:

了解SpringBoot的主动安装

6、小结

本文环绕 SpringBoot 的中心功用展开,从总体上了解 SpringBoot 主动装备的原理以及主动装备中心组件的运作进程,要点学习主动装备原理、@EnableAutoConfiguration、 @lmport、 ImportSelector、@Conditional 以及 starter 示例解析等内容。把握了这些根底的组成内容及其功用,在集成其他三方类库的主动装备时,才可以更加清晰地了解它们都运用了主动装备的哪些功用。