源码中常见的 @Conditional 注解

起因

    我为什么要研究这种注解,平时写业务代码的时候并不会用到这种的注解,根据条件去注入不同的类,但是我在看 spring cloud 源码的过程中遇到了类似于 @Conditional【符合条件才会被加载】、@ConditionalOnMissingBean【某个 Bean 不存在于应用上下文时才会加载】、@ConditionalOnBean【某个 Bean 存在于应用上下文时才会加载】、@ConditionalOnClass 【某个类是否存在于 classpath 中】等等这样的注解,比如下面框架中的源码

Ribbon中的部分源码

简单案例讲解源码中常见的 @Conditional 注解_ide

ShardingSphere中的源码

简单案例讲解源码中常见的 @Conditional 注解_Spring_02

    很多框架里面都含有大量的这样的注解所以必须得研究明白不然读源码的时候,甚至重写代码的时候会特别的蒙

作用

    可以根据代码中设置的条件装载不同的bean,比如说当一个接口有两个实现类时,我们要把这个接口交给Spring管理时通常会只选择实现其中一个实现类,所以这个@Conditional的注解就出现了,但是如果不用这个注解的话,那么这两个类都会被容器加载进来。

不使用@Conditional注解

1. 声明一个名字输出接口服务

    该接口的作用只是用来输出名字

interface NameService {
// 只用来输出名字用
void soutName();
}

2. 声明两个水果类实现该接口

    声明两个类分别实现这两个接口,用来打印名字服务

class AppleNameService implements NameService {
@Override
public void soutName() {
System.out.println("apple");
}
}

class PeachNameService implements NameService {
@Override
public void soutName() {
System.out.println("peach");
}
}

3. 声明一个水果配置类

    声明一个水果配置类,里面定义以上两个实例,之后都给一个 @bean 注解,都注入到spring容器中

@Configuration
class FruitConfiguration {
@Bean(name = "peachService")
public NameService peachService() {
return new PeachNameService();
}
@Bean(name = "appleService")
public NameService appleService() {
return new AppleNameService();
}
}

4. 在main方法里面获取一下该 NameService 服务都注入了哪些实例

public class ConditionalTestController {
public static void main(String[] args) {
AnnotationConfigApplicationContext configApplicationContext =
new AnnotationConfigApplicationContext(FruitConfiguration.class);
Map<String, NameService> map =
configApplicationContext.getBeansOfType(NameService.class);
map.forEach((key, value) -> {
map.get(key).soutName();
});
configApplicationContext.close();
}
}

5. 运行结果

简单案例讲解源码中常见的 @Conditional 注解_加载_03

    根据结果我们看出来就是配置类里面的所有的实例都被注入进来了,但是如果在某一种情况下只想让桃子服务实例注入,不让苹果实例服务注入该怎么做呢,那么就需要用到 @Conditional 注解了

使用@Condiational注解

    我们先写代码之后再讲为什么要这么写

1. 声明 Condition 的注解

    从名字来看就是一个条件的意思,如果符合某些条件那么就注入该实例,不符合那么就不注入,那么我们就取服务名字来进行判断,如果是 **peachService **那么就返回true,其他的返回false

class FuritCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取服务名字
String name = ((StandardMethodMetadata) metadata).
getIntrospectedMethod().getName();
if (name.equals("peachService")) return true; // 设置使桃子条件不成立
return false;
}
}

    注意返回值设置一个是true,一个是false,因为我们要用苹果服务实例所以把苹果的设置为true

2. 给配置类中的实例加上条件注解

@Configuration
class FruitConfiguration {
@Bean(name = "peachService")
@Conditional(FuritCondition.class) // 桃子服务条件注解
public NameService peachService() {
return new PeachNameService();
}

@Bean(name = "appleService") // 苹果服务条件注解
@Conditional(FuritCondition.class)
public NameService appleService() {
return new AppleNameService();
}
}

3. 运行结果

简单案例讲解源码中常见的 @Conditional 注解_Spring_04

    从结果上来看就是只有桃子服务注册上去了,如果我们把matches方法的返回值都设置成true那么回事什么样子的呢

class FuritCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取服务名字
// String name = ((StandardMethodMetadata) metadata).
// getIntrospectedMethod().getName();
// if (name.equals("peachService")) return true; // 设置使桃子条件不成立
return false;
}
}

4. 运行结果

简单案例讲解源码中常见的 @Conditional 注解_ide_05

    两个实例都注入进来了,那就是说明这个使用了@Conditional 注解的类,这个xxxCondition里面的matches方法,如果设置为true那么就注入进来,如果不是的话那么就不注入进来

@Conditional 源码细节

    我们来看看@Conditional 注解中的源码信息

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
// 必须匹配的所有条件才能注册组件
Class<? extends Condition>[] value();
}

@Conditional 使用方法

    根据源码显示就是,这个注解接收的参数就是实现了 Condition 接口的类,而且必须所有的匹配条件都是成立的才能被注册到spring容器中去

@Condition 源码细节

// 在bean定义即将注册之前,会立即检查条件,并且可以根据当时可以确定的任何条件决定是否进行注册
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

    实现了 Condition 接口的类都必须实现 matches 方法,只有实现了这个 matches 并且符合所有条件的类才能被注入到Spring容器中去