该系列文章是笔者在学习 spring boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 spring boot 源码分析 github 地址 进行阅读

spring boot 版本:2.2.x

最好对 spring 源码有一定的了解,可以先查看我的 《死磕 spring 之 ioc 篇 – 文章导读》 系列文章

如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~

该系列其他文章请查看:《精尽 spring boot 源码分析 – 文章导读》

概述

在上一篇《剖析 @springbootapplication 注解》文章分析了 spring boot 的自动配置功能,在通过 @enableautoconfiguration 注解驱动整个自动配置模块的过程中,并不是所有的自动配置类都需要被注入,不同的自动配置类需要满足一定条件后,才应该进行自动配置

那么 spring boot 怎么知道满足一定条件呢?spring boot 对 spring 的 condition 接口进行了扩展,然后结合自定义的注解,则可以判断自动配置类是否符合条件。

例如 @conditionalonclass 可以指定必须存在哪些 class 对象才注入这个 bean。

那么接下来,我们一起来看看 spring 的 condition 接口以及 spring boot 对其的扩展

condition 演进史

profile 的出场

在 spring 3.1 的版本,为了满足不同环境注册不同的 bean ,引入了 @profile 注解。例如:

@configuration
public class datasourceconfiguration {

    @bean
    @profile("dev")
    public datasource devdatasource() {
        // ... 单机 mysql
    }

    @bean
    @profile("prod")
    public datasource proddatasource() {
        // ... 集群 mysql
    }
}
  • 在测试环境下,我们注册单机 mysql 的 datasource bean
  • 在生产环境下,我们注册集群 mysql 的 datasource bean

spring 3.1.x 的 @profile 注解如下:

@retention(retentionpolicy.runtime)
@target(elementtype.type)
public @interface profile {

	/**
	 * the set of profiles for which this component should be registered.
	 */
	string[] value();
}

可以看到,最开始 @profile 注解并没有结合 @conditional 注解一起使用,而是在后续版本才引入的

condition 的出现

在 spring 4.0 的版本,出现了 condition 功能,体现在 org.springframework.context.annotation.condition 接口,如下:

@functionalinterface
public interface condition {

	/**
	 * determine if the condition matches.
	 * @param context the condition context
	 * @param metadata the metadata of the {@link org.springframework.core.type.annotationmetadata class}
	 * or {@link org.springframework.core.type.methodmetadata method} being checked
	 * @return {@code true} if the condition matches and the component can be registered,
	 * or {@code false} to veto the annotated component's registration
	 */
	boolean matches(conditioncontext context, annotatedtypemetadata metadata);
}

函数式接口,只有一个 matches(..) 方法,判断是否匹配,从入参中可以知道,它是和注解配合一起实现 condition 功能的,也就是 @conditional 注解,如下:

@target({elementtype.type, elementtype.method})
@retention(retentionpolicy.runtime)
@documented
public @interface conditional {

	/**
	 * all {@link condition} classes that must {@linkplain condition#matches match}
	 * in order for the component to be registered.
	 */
	class<? extends condition>[] value();
}

随之 @profile 注解也进行了修改,和 @conditional 注解配合使用

spring 5.1.x 的 @profile 注解如下:

@target({elementtype.type, elementtype.method})
@retention(retentionpolicy.runtime)
@documented
@conditional(profilecondition.class)
public @interface profile {

	/**
	 * the set of profiles for which the annotated component should be registered.
	 */
	string[] value();
}

这里指定的的 condition 实现类是 profilecondition,如下:

class profilecondition implements condition {

	@override
	public boolean matches(conditioncontext context, annotatedtypemetadata metadata) {
		multivaluemap<string, object> attrs = metadata.getallannotationattributes(profile.class.getname());
		if (attrs != null) {
			for (object value : attrs.get("value")) {
				if (context.getenvironment().acceptsprofiles(profiles.of((string[]) value))) {
					return true;
				}
			}
			return false;
		}
		return true;
	}
}

逻辑很简答,从当前 spring 应用上下文的 environment 中判断 @profile 指定的环境是否被激活,被激活了表示匹配成功,则注入对应的 bean,否则,不进行操作

但是 spring 本身提供的 condition 实现类不多,只有一个 profilecondition 对象

springbootcondition 的进击

spring boot 为了满足更加丰富的 condition 场景,对 spring 的 condition 接口进行了扩展,提供更多的实现类,如下:

上面仅列出了部分 springbootcondition 的子类,同时这些子类与对应的注解配置一起使用

  • @conditionalonclass:必须都存在指定的 class 对象们
  • @conditionalonmissingclass:指定的 class 对象们必须都不存在
  • @conditionalonbean:必须都存在指定的 bean 们
  • @conditionalonmissingbean:指定的 bean 们必须都不存在
  • @conditionalonsinglecandidate:必须存在指定的 bean
  • @conditionalonproperty:指定的属性是否有指定的值
  • @conditionalonwebapplication:当前的 web 应用类型是否在指定的范围内(any、servlet、reactive)
  • @conditionalonnotwebapplication:不是 web 应用类型

上面列出了 spring boot 中常见的几种 @conditionxxx 注解,他们都配合 @conditional 注解与对应的 condition 实现类一起使用,提供了非常丰富的 condition 场景

condition 在哪生效?

spring 提供了 condition 接口以及 @conditional 注解,那么在 spring 中哪里体现,或者说是哪里进行判断的呢?

其实我在 《死磕spring之ioc篇 – @bean 等注解的实现原理》 这篇文章中有提到过,我们稍微回顾一下,有两种情况:

  • 通过 @component 注解(及派生注解)标注的 bean
  • @configuration 标注的配置类中的 @bean 标注的方法 bean

普通 bean

第一种情况是在 spring 扫描指定路径下的 .class 文件解析成对应的 beandefinition(bean 的前身)时,会根据 @conditional 注解判断是否符合条件,如下:

// classpathbeandefinitionscanner.java
public int scan(string... basepackages) {
    // <1> 获取扫描前的 beandefinition 数量
    int beancountatscanstart = this.registry.getbeandefinitioncount();

    // <2> 进行扫描,将过滤出来的所有的 .class 文件生成对应的 beandefinition 并注册
    doscan(basepackages);

    // register annotation config processors, if necessary.
    // <3> 如果 `includeannotationconfig` 为 `true`(默认),则注册几个关于注解的 postprocessor 处理器(关键)
    // 在其他地方也会注册,内部会进行判断,已注册的处理器不会再注册
    if (this.includeannotationconfig) {
        annotationconfigutils.registerannotationconfigprocessors(this.registry);
    }

    // <4> 返回本次扫描注册的 beandefinition 数量
    return (this.registry.getbeandefinitioncount() - beancountatscanstart);
}
// classpathscanningcandidatecomponentprovider.java
private boolean isconditionmatch(metadatareader metadatareader) {
    if (this.conditionevaluator == null) {
        this.conditionevaluator =
                new conditionevaluator(getregistry(), this.environment, this.resourcepatternresolver);
    }
    return !this.conditionevaluator.shouldskip(metadatareader.getannotationmetadata());
}

上面只是简单的提一下,可以看到会通过 conditionevaluator 计算器进行计算,判断是否满足条件

配置类

第二种情况是 spring 会对 配置类进行处理,扫描到带有 @bean 注解的方法,尝试解析成 beandefinition(bean 的前身)时,会根据 @conditional 注解判断是否符合条件,如下:

// configurationclassbeandefinitionreader.java
private void loadbeandefinitionsforconfigurationclass(
        configurationclass configclass, trackedconditionevaluator trackedconditionevaluator) {

    // <1> 如果不符合 @conditional 注解的条件,则跳过
    if (trackedconditionevaluator.shouldskip(configclass)) {
        string beanname = configclass.getbeanname();
        if (stringutils.haslength(beanname) && this.registry.containsbeandefinition(beanname)) {
            this.registry.removebeandefinition(beanname);
        }
        this.importregistry.removeimportingclass(configclass.getmetadata().getclassname());
        return;
    }

    // <2> 如果当前 configurationclass 是通过 @import 注解被导入的
    if (configclass.isimported()) {
        // <2.1> 根据该 configurationclass 对象生成一个 beandefinition 并注册
        registerbeandefinitionforimportedconfigurationclass(configclass);
    }
    // <3> 遍历当前 configurationclass 中所有的 @bean 注解标注的方法
    for (beanmethod beanmethod : configclass.getbeanmethods()) {
        // <3.1> 根据该 beanmethod 对象生成一个 beandefinition 并注册(注意这里有无 static 修饰会有不同的配置)
        loadbeandefinitionsforbeanmethod(beanmethod);
    }

    // <4> 对 @importresource 注解配置的资源进行处理,对里面的配置进行解析并注册 beandefinition
    loadbeandefinitionsfromimportedresources(configclass.getimportedresources());
    // <5> 通过 @import 注解导入的 importbeandefinitionregistrar 实现类往 beandefinitionregistry 注册 beandefinition
    // mybatis 集成 spring 就是基于这个实现的,可查看 mybatis-spring 项目中的 mapperscannerregistrar 这个类
    // https://github.com/liu844869663/mybatis-spring/blob/master/src/main/java/org/mybatis/spring/annotation/mapperscannerregistrar.java
    loadbeandefinitionsfromregistrars(configclass.getimportbeandefinitionregistrars());
}

上面只是简单的提一下,可以看到会通过 trackedconditionevaluator 计算器进行计算,判断是否满足条件

这里提一下,对于 @bean 标注的方法,会得到 cglib 的提升,也就是返回的是一个代理对象,设置一个拦截器专门对 @bean 方法进行拦截处理,通过依赖查找的方式从 ioc 容器中获取 bean 对象,如果是单例 bean,那么每次都是返回同一个对象,所以当主动调用这个方法时获取到的都是同一个对象。

springbootcondition

org.springframework.boot.autoconfigure.condition.springbootcondition 抽象类,实现了 condition 接口,spring boot 扩展 condition 的抽象基类,主要用于打印相应的日志,并记录每次的匹配结果,如下:

/**
 * base of all {@link condition} implementations used with spring boot. provides sensible
 * logging to help the user diagnose what classes are loaded.
 *
 * @author phillip webb
 * @author greg turnquist
 * @since 1.0.0
 */
public abstract class springbootcondition implements condition {

	private final log logger = logfactory.getlog(getclass());

	@override
	public final boolean matches(conditioncontext context, annotatedtypemetadata metadata) {
		// <1> 从注解元信息中获取所标注的`类名`(或者`类名#方法名`)
		string classormethodname = getclassormethodname(metadata);
		try {
			// <2> 获取匹配结果(包含匹配消息),抽象方法,交由子类实现
			conditionoutcome outcome = getmatchoutcome(context, metadata);
			// <3> 打印匹配日志
			logoutcome(classormethodname, outcome);
			// <4> 向 conditionevaluationreport 中记录本次的匹配结果
			recordevaluation(context, classormethodname, outcome);
			// <5> 返回匹配结果
			return outcome.ismatch();
		}
		catch (noclassdeffounderror ex) {
			// 抛出异常
		} catch (runtimeexception ex) {
			// 抛出异常
		}
	}
}

实现的 condition 接口方法处理过程如下:

  1. 从注解元信息中获取所标注的类名(或者类名#方法名

    private static string getclassormethodname(annotatedtypemetadata metadata) {
        if (metadata instanceof classmetadata) {
            classmetadata classmetadata = (classmetadata) metadata;
            return classmetadata.getclassname();
        }
        methodmetadata methodmetadata = (methodmetadata) metadata;
        return methodmetadata.getdeclaringclassname() + "#" + methodmetadata.getmethodname();
    }
    
  2. 调用 getmatchoutcome(..) 方法,获取匹配结果(包含匹配消息),抽象方法,交由子类实现

  3. 打印匹配日志

    protected final void logoutcome(string classormethodname, conditionoutcome outcome) {
        if (this.logger.istraceenabled()) {
            this.logger.trace(getlogmessage(classormethodname, outcome));
        }
    }
    
  4. 向 conditionevaluationreport 中记录本次的匹配结果

    private void recordevaluation(conditioncontext context, string classormethodname, conditionoutcome outcome) {
        if (context.getbeanfactory() != null) {
            conditionevaluationreport.get(context.getbeanfactory()).recordconditionevaluation(classormethodname, this, outcome);
        }
    }
    
  5. 返回匹配结果

springbootcondition 的实现类

onclasscondition

org.springframework.boot.autoconfigure.condition.onclasscondition,继承 springbootcondition 抽象类,如下:

@order(ordered.highest_precedence)
class onclasscondition extends filteringspringbootcondition {
    /**
	 * 该方法来自 {@link springbootcondition} 判断某个 bean 是否符合注入条件(`@conditionalonclass` 和 `conditionalonmissingclass`)
	 */
	@override
	public conditionoutcome getmatchoutcome(conditioncontext context, annotatedtypemetadata metadata) {
		classloader classloader = context.getclassloader();
		conditionmessage matchmessage = conditionmessage.empty();
		// <1> 获取这个类上面的 `@conditionalonclass` 注解的值
		// 也就是哪些 class 对象必须存在
		list<string> onclasses = getcandidates(metadata, conditionalonclass.class);
		if (onclasses != null) {
			// <1.1> 找到这些 class 对象中哪些是不存在的
			list<string> missing = filter(onclasses, classnamefilter.missing, classloader);
			// <1.2> 如果存在不存在的,那么不符合条件,返回不匹配
			if (!missing.isempty()) {
				return conditionoutcome.nomatch(conditionmessage.forcondition(conditionalonclass.class)
						.didnotfind("required class", "required classes").items(style.quote, missing));
			}
			// <1.3> 添加 `@conditionalonclass` 满足条件的匹配信息
			matchmessage = matchmessage.andcondition(conditionalonclass.class)
					.found("required class", "required classes")
					.items(style.quote, filter(onclasses, classnamefilter.present, classloader));
		}
		// <2> 获取这个类上面的 `@conditionalonmissingclass` 注解的值
		// 也就是这些 class 对象必须都不存在
		list<string> onmissingclasses = getcandidates(metadata, conditionalonmissingclass.class);
		if (onmissingclasses != null) {
			// <2.1> 找到这些 class 对象中哪些是存在的
			list<string> present = filter(onmissingclasses, classnamefilter.present, classloader);
			// <2.2> 如果有一个存在,那么不符合条件,返回不匹配
			if (!present.isempty()) {
				return conditionoutcome.nomatch(conditionmessage.forcondition(conditionalonmissingclass.class)
						.found("unwanted class", "unwanted classes").items(style.quote, present));
			}
			// <2.3> 添加 `@conditionalonmissingclass` 满足条件的匹配信息
			matchmessage = matchmessage.andcondition(conditionalonmissingclass.class)
					.didnotfind("unwanted class", "unwanted classes")
					.items(style.quote, filter(onmissingclasses, classnamefilter.missing, classloader));
		}
		// <3> 返回符合条件的结果
		return conditionoutcome.match(matchmessage);
	}
}

判断是否匹配的过程如下:

  1. 获取这个类上面的 @conditionalonclass 注解的值,也就是哪些 class 对象必须存在

    1. 找到这些 class 对象中哪些是不存在的

      protected final list<string> filter(collection<string> classnames, classnamefilter classnamefilter,
              classloader classloader) {
          // 如果为空,则返回空结果
          if (collectionutils.isempty(classnames)) {
              return collections.emptylist();
          }
          list<string> matches = new arraylist<>(classnames.size());
          // 使用 `classnamefilter` 对 `classnames` 进行过滤
          for (string candidate : classnames) {
              if (classnamefilter.matches(candidate, classloader)) {
                  matches.add(candidate);
              }
          }
          // 返回匹配成功的 `classname` 们
          return matches;
      }
      
    2. 如果存在不存在的,那么不符合条件,返回不匹配

    3. 添加 @conditionalonclass 满足条件的匹配信息

  2. 获取这个类上面的 @conditionalonmissingclass 注解的值,也就是这些 class 对象必须都不存在

    1. 找到这些 class 对象中哪些是存在的,和上面的 1.1 差不多,只不过这里传的是 classnamefilter.present 过滤器
    2. 如果有一个存在,那么不符合条件,返回不匹配
    3. 添加 @conditionalonmissingclass 满足条件的匹配信息
  3. 返回符合条件的结果

上面使用到的 classnamefilter 如下:

protected enum classnamefilter {

    /** 指定类存在 */
    present {
        @override
        public boolean matches(string classname, classloader classloader) {
            return ispresent(classname, classloader);
        }
    },

    /** 指定类不存在 */
    missing {
        @override
        public boolean matches(string classname, classloader classloader) {
            return !ispresent(classname, classloader);
        }
    };

    abstract boolean matches(string classname, classloader classloader);

    static boolean ispresent(string classname, classloader classloader) {
        if (classloader == null) {
            classloader = classutils.getdefaultclassloader();
        }
        try {
            // 加载指定类,加载成功表示存在这个类
            resolve(classname, classloader);
            return true;
        }
        catch (throwable ex) {
            // 加载失败表示不存在这个类
            return false;
        }
    }
}

逻辑很简单,就是判断 class 对象是否存在或者不存在

其它实现类

关于 springbootcondition 其他的实现类逻辑都差不多,感兴趣的可以去看看

回顾自动配置

在上一篇《剖析 @springbootapplication 注解》 文章分析通过 @enableautoconfiguration 注解驱动整个自动配置模块的过程中,会通过指定的 autoconfigurationimportfilter 对所有的自动配置类进行过滤,满足条件才进行自动配置

可以回顾一下上一篇文章的 2. getautoconfigurationentry 方法 小节和 3. filter 方法 小节

protected autoconfigurationentry getautoconfigurationentry(autoconfigurationmetadata autoconfigurationmetadata,
        annotationmetadata annotationmetadata) {
    // <1> 如果通过 `spring.boot.enableautoconfiguration` 配置关闭了自动配置功能
    if (!isenabled(annotationmetadata)) {
        // 则返回一个“空”的对象
        return empty_entry;
    }
    // <2> 获取 `@enableautoconfiguration` 注解的配置信息
    annotationattributes attributes = getattributes(annotationmetadata);
    // <3> 从所有的 `meta-inf/spring.factories` 文件中找到 `@enableautoconfiguration` 注解对应的类(需要自动配置的类)
    list<string> configurations = getcandidateconfigurations(annotationmetadata, attributes);
    // <4> 对所有的自动配置类进行去重
    configurations = removeduplicates(configurations);
    // <5> 获取需要排除的自动配置类
    // 可通过 `@enableautoconfiguration` 注解的 `exclude` 和 `excludename` 配置
    // 也可以通过 `spring.autoconfigure.exclude` 配置
    set<string> exclusions = getexclusions(annotationmetadata, attributes);
    // <6> 处理 `exclusions` 中特殊的类名称,保证能够排除它
    checkexcludedclasses(configurations, exclusions);
    // <7> 从 `configurations` 中将 `exclusions` 需要排除的自动配置类移除
    configurations.removeall(exclusions);
    /**
     * <8> 从 `meta-inf/spring.factories` 找到所有的 {@link autoconfigurationimportfilter} 对 `configurations` 进行过滤处理
     * 例如 spring boot 中配置了 {@link org.springframework.boot.autoconfigure.condition.onclasscondition}
     * 在这里提前过滤掉一些不满足条件的自动配置类,在 spring 注入 bean 的时候也会判断哦~
     */
    configurations = filter(configurations, autoconfigurationmetadata);
    fireautoconfigurationimportevents(configurations, exclusions);
    // <10> 将所有的自动配置类封装成一个 autoconfigurationentry 对象,并返回
    return new autoconfigurationentry(configurations, exclusions);
}

我们看到第 8 步,调用 filter(..) 方法, 目的就是过滤掉一些不符合 condition 条件的自动配置类

private list<string> filter(list<string> configurations, autoconfigurationmetadata autoconfigurationmetadata) {
    long starttime = system.nanotime();
    // <1> 将自动配置类保存至 `candidates` 数组中
    string[] candidates = stringutils.tostringarray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    /*
     * <2> 从 `meta-inf/spring.factories` 找到所有的 autoconfigurationimportfilter 对 `candidates` 进行过滤处理
     * 有 onclasscondition、onbeancondition、onwebapplicationcondition
     */
    for (autoconfigurationimportfilter filter : getautoconfigurationimportfilters()) {
        // <2.1> aware 回调
        invokeawaremethods(filter);
        // <2.2> 对 `candidates` 进行匹配处理,获取所有的匹配结果
        boolean[] match = filter.match(candidates, autoconfigurationmetadata);
        // <2.3> 遍历匹配结果,将不匹配的自动配置类至空
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    // <3> 如果没有不匹配的结果则全部返回
    if (!skipped) {
        return configurations;
    }
    // <4> 获取到所有匹配的自动配置类,并返回
    list<string> result = new arraylist<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    return new arraylist<>(result);
}

可以看到第 2 步,会从 meta-inf/spring.factories 中找到对应的 autoconfigurationimportfilter 实现类对所有的自动配置类进行过滤

# auto configuration import filters
org.springframework.boot.autoconfigure.autoconfigurationimportfilter=\
org.springframework.boot.autoconfigure.condition.onbeancondition,\
org.springframework.boot.autoconfigure.condition.onclasscondition,\
org.springframework.boot.autoconfigure.condition.onwebapplicationcondition

这里,我们一定要注意到入参中的 autoconfigurationmetadata 对象,它里面保存了 meta-inf/spring-autoconfigure-metadata.properties 文件中 spring boot 的自动配置类的注解元信息(sprng boot 编译时生成的),如何来的请回顾上一篇文章

autoconfigurationimportfilter

org.springframework.boot.autoconfigure.autoconfigurationimportfilter 接口,用于过滤掉无需自动引入的自动配置类

/**
 * filter that can be registered in {@code spring.factories} to limit the
 * auto-configuration classes considered. this interface is designed to allow fast removal
 * of auto-configuration classes before their bytecode is even read.
 *
 * @author phillip webb
 * @since 1.5.0
 */
@functionalinterface
public interface autoconfigurationimportfilter {

	boolean[] match(string[] autoconfigurationclasses, autoconfigurationmetadata autoconfigurationmetadata);
}

可以看到它的注释,因为自动配置类会很多,如果无需使用,而创建对应的 bean(字节码)到 jvm 内存中,将是一种浪费

可以看到它的最终实现类,都是构建在 springbootcondition 之上。