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
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 MultiValueMapattrs = 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的介绍也就告一段落了。下章,我们将会开始介绍混合配置。欢迎大家继续阅读,谢谢大家!
下载代码