@Conditional
注解是 Spring 4 提供的基于条件的 Bean 的创建方式,Spring Boot 大量利用了这个特定来实现自动配置。比如,当某一个 jar 包在一个类路径下时,自动配置一个或者多个 Bean;或者只有一个 Bean 创建时,才会创建另一个 Bean。总的来说,就是根据特定条件来控制 Bean 的创建行为,这样就可以利用这个特性进行一些自动配置。
自定义 Condition 实例
下面的示例将以不同的操作系统作为条件,通过实现 Condition 接口,并重写其 matches 方法来构造判断条件,获取在不同操作系统下的操作命令。如在 Windows 系统下运行程序调用获取文件列表命名的方法则输出 dir
,如果在 Linux 下则输出 ls
。
通过实现 Spring 提供的 Condition
接口创建两个 Condition 类
自定义 Condition 需要实现 org.springframework.context.annotation.Condition
接口中的 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
方法,我们的条件判断逻辑则应该放在此方法中。
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println("os.name:" + context.getEnvironment().getProperty("os.name"));
return context.getEnvironment().getProperty("os.name").contains("Windows");
}
}
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println("os.name:" + context.getEnvironment().getProperty("os.name"));
return context.getEnvironment().getProperty("os.name").contains("Linux");
}
}
创建获取命令的接口并分别创建 Linux 和 Windows 下的实现
public interface CmdService {
String getListCmd();
}
public class WindowsCmdServiceImpl implements CmdService {
@Override
public String getListCmd() {
return "dir";
}
}
public class LinuxCmdServiceImpl implements CmdService {
@Override
public String getListCmd() {
return "ls";
}
}
创建配置类并使用 @Conditional
注解将 Bean 定义为条件创建
@Configuration
public class SpringConditionalConf {
@Bean
@Conditional(WindowsCondition.class) //WindowsCondition 条件成立时创建此 Bean
public CmdService windowsCmdService(){
return new WindowsCmdServiceImpl();
}
@Bean
@Conditional(LinuxCondition.class) //LinuxCondition 条件成立时创建此 Bean
public CmdService linuxCmdService(){
return new LinuxCmdServiceImpl();
}
}
测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = HelloSpringBoot.class)
public class SpringConditionalDemoTest {
@Autowired
private CmdService cmdService;
@Test
public void testConditional(){
System.out.println(cmdService.getListCmd());
}
}
在 windows 主机上运行测试得到输出 dir
,说明只有 WindowsCmdServiceImpl
实例被创建并且得到了正确的指令。
@Conditional
常见使用方法
在上例中 @Conditional
注解标注到 @Bean
的方法上;除此之外还可以作为类级别的注解放在注标识有@Component(包含@Configuration)的类上,这时所有标识了 @Bean
的方法和 @Import
注解导入的相关类将遵从这些条件;最后还可以作为一个 meta-annotation,组成自定义注解。
Spring 内置 Conditional
除了类似上例的自定义 Condition 之外,Spring 还内置了一些 Condition 给我们使用:
@ConditionalOnBean
仅仅在当前上下文中存在某个对象时,才会实例化一个 Bean@ConditionalOnClass
某个 class 位于类路径上,才会实例化一个 Bean@ConditionalOnExpression
当表达式为 true 的时候,才会实例化一个 Bean@ConditionalOnMissingBean
仅仅在当前上下文中不存在某个对象时,才会实例化一个 Bean@ConditionalOnMissingClass
某个 class 类路径上不存在的时候,才会实例化一个 Bean@ConditionalOnNotWebApplication
不是 web 应用时才会实例化一个 Bean
@Conditional
与 @Profile
区别
Spring 3.1 推出的 @Profiles 注解功能与 @Conditional 注解类似,都是提供一种 “If-Then-Else” 能力,即条件配置功能。但是更早出现的 @Profiles 主要是用来根据不同的运行环境加载不同的应用配置;@Conditional 注解是一种更高层次的实现,他没有 @Profile 注解的一些限制,是 @profile 的一种更加通用的版本,其主要被用作 Bean 的条件加载。
@Profile 注解使用举例
@Profile("Development")
@Configuration
public class DevDatabaseConfig implements DatabaseConfig {
@Override
@Bean
public DataSource createDataSource() {
System.out.println("Creating DEV database");
DriverManagerDataSource dataSource = new DriverManagerDataSource();
/*
* Set MySQL specific properties for Development Environment
*/
return dataSource;
}
}
@Profile("Production")
@Configuration
public class ProductionDatabaseConfig implements DatabaseConfig {
@Override
@Bean
public DataSource createDataSource() {
System.out.println("Creating Production database");
DriverManagerDataSource dataSource = new DriverManagerDataSource();
/*
* Set ORACLE specific properties for Production environment
*/
return dataSource;
}
}
以上两个配置类都实现了 DatabaseConfig 接口,特殊的地方在于它们都用 @Profile 标注,被 @Profile 标注的组件只有当指定 profile 值匹配时才生效。可以通过以下方式设置 profile 值:
- 设置 spring.profiles.active 属性(通过 JVM 参数、环境变量或者 web.xml 中的 Servlet context 参数)
- ApplicationContext.getEnvironment().setActiveProfiles(“ProfileName”)
在 Spring 3.x 里 @Profiles 注解只能用在类级别,但是在 Spring 4.0 以后则既可以用在类级别也可以用在方法级别,主要是因为在 4.0 中 Spring 使用 @Conditional 对其做了重构:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class) // 使用 @Conditional 实现 @Profile
public @interface Profile {
String[] value();
}
@Conditional
注解使用场景
我们知道在 Spring Boot 中大量使用了 @Conditional
注解,我们在平时的开发中如果遇到以下一些场景则可以考虑使用该注解:
- Condition whether a property is available or not using Environment variables, irrespective of its value.
- Like Profiles, Condition whether a property value is available or not using Environment variables.
- Conditions based on a Bean definition are present in Spring Application context.
- Conditions based on a Bean object are present in Spring Application context.
- Conditions based on some or all Bean properties values.
- Conditions based on some Resources are present in current Spring Application Context or not.
- Conditions based on Bean’s Annotations
- Conditions Bean’s Method’s Annotations.
- Conditions based on Bean’s Annotation’s parameter values
- Conditions based on Bean’s Method’s Annotation’s parameter values.