Condition


Profile是个好东西。通过Profile,我们可以非常方便地条件化Bean的创建,动态调整应用程序的功能。可是,Profile只能做些简单的条件化,对于复杂一点的条件化Profile是无法胜任的。比如现有这样的数据源创建需求:
1.如果类路径存在DBCP的JAR包,则创建DBCP提供的BasicDataSource数据源。
2.如果类路径没有DBCP的JAR包,则创建Spring提供的DriverManagerDataSource数据源。

毫无疑问,这样的需求Profile是实现不了的。要想实现这样的需求,还得仰赖Spring提供的,专门用于Bean的条件化创建的,功能远比Profile强大的Condition。而这,需要我们做好两件事情:
1.实现Condition接口,以描述Bean的创建条件。
2.往配置方法添加@Conditional注解,告诉Spring容器创建Bean时以某个实现了Condition接口的类作为条件。

Condition接口签名如下:

1 @FunctionalInterface
2 public interface Condition {
3     boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
4 }

其中定义的matches方法返回一个布尔类型的值:如果返回的是TRUE,说明条件成立,Spring容器将会创建相应的Bean;如果返回的是FALSE,说明条件失败,Spring容器不会创建相应的Bean

另外,matches方法还接受两个参数:一个参数是ConditionContext类型的,能向我们提供一些诸如Bean的定义,环境变量之类的信息;一个参数是AnnotatedTypeMetadata类型的,能向我们提供一些诸如配置方法是否带有某种注解之类的信息。我们实现matches方法的时候,能用这些信息进行条件检查。

因此,为了实现文章开头提到的需求,我们首先需要做的就是这样实现Condition接口:

 1 public class ConditionOnDriverManagerDataSource implements Condition {
 2     @Override
 3     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
 4         var dbcpFileName = "WEB-INF\\lib\\commons-dbcp2-2.8.0.jar";
 5         var resourceLoader = context.getResourceLoader();
 6         var resource = resourceLoader.getResource(dbcpFileName);
 7         return !resource.exists();
 8     }
 9 }
10 
11 public class ConditionOnBasicDataSource implements Condition {
12     @Override
13     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
14         var dbcpFileName = "WEB-INF\\lib\\commons-dbcp2-2.8.0.jar";
15         var resourceLoader = context.getResourceLoader();
16         var resource = resourceLoader.getResource(dbcpFileName);
17         return resource.exists();
18     }
19 }

这段代码定义的两个类都实现了Condition接口:

1.ConditionOnDriverManagerDataSource能够检查类路径是否存在DBCP的JAR包:如果存在则返回FALSE;否则返回TRUE。这个条件能够告诉Spring容器只有类路径没有DBCP的JAR包时,才会创建相应的Bean
2.ConditionOnBasicDataSource能够检查类路径是否存在DBCP的JAR包:如果存在则返回TRUE;否则返回FALSE。这个条件能够告诉Spring容器只有类路径存在DBCP的JAR包时,才会创建相应的Bean

于是,我们完成了Condition接口的实现,该把它们交给@Conditional注解进行Bean的条件化配置了。@Conditional注解有个Class类型的value属性,用于指定实现了Condition接口的类的Class对象,告诉Spring容器创建Bean时以哪个Condition作为条件。如下所示:

 1 @Configuration
 2 public class AppConfig {
 3     @Bean
 4     @Conditional(value = ConditionOnDriverManagerDataSource.class)
 5     public DataSource produceDataSource() {
 6         var dataSource = new DriverManagerDataSource();
 7         dataSource.setUsername("root");
 8         dataSource.setPassword("123456");
 9         dataSource.setUrl("jdbc:mysql://localhost:3306/sm_person_rest");
10         dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
11         return dataSource;
12     }
13 
14     @Bean
15     @Conditional(value = ConditionOnBasicDataSource.class)
16     public DataSource produceBasicDataSource() {
17         var dataSource = new BasicDataSource();
18         dataSource.setUsername("root");
19         dataSource.setPassword("123456");
20         dataSource.setUrl("jdbc:mysql://localhost:3306/sm_person_rest");
21         dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
22         return dataSource;
23     }
24 }

这段配置代码定义了两个方法:
1.方法produceDataSource带有@Conditional(ConditionOnDriverManagerDataSource.class)注解。Spring容器瞧见这个注解之后,就会调用ConditionOnDriverManagerDataSource的matches方法进行条件判断:如果matches方法返回TRUE,则创建DriverManagerDataSource数据源;否则不创建。
2.方法produceBasicDataSource带有@Conditional(ConditionOnBasicDataSource.class)注解。Spring容器瞧见这个注解之后,就会调用ConditionOnBasicDataSource的matches方法进行条件判断:如果matches方法返回TRUE,则创建BasicDataSource数据源;否则不创建。

如此一来,Spring容器就能根据类路径是否存在DBCP的JAR包决定创建哪种数据源了。有趣的是,前文介绍的Profile其实也是Condition的一种实现。如下所示:

 1 @Target({ElementType.TYPE, ElementType.METHOD})
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 @Conditional({ProfileCondition.class})
 5 public @interface Profile {
 6     String[] value();
 7 }
 8 
 9 class ProfileCondition implements Condition {
10     public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
11         MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
12         if (attrs != null) {
13             Iterator var4 = ((List)attrs.get("value")).iterator();
14 
15             Object value;
16             do {
17                 if (!var4.hasNext()) {
18                     return false;
19                 }
20 
21                 value = var4.next();
22             } while(!context.getEnvironment().acceptsProfiles(Profiles.of((String[])((String[])value))));
23 
24             return true;
25         } else {
26             return true;
27         }
28     }
29 }

至此,关于Condition的介绍也就告一段落了。下章,我们将会开始介绍混合配置。欢迎大家继续阅读,谢谢大家!

    下载代码