转载自:https://blog.youkuaiyun.com/doctor_who2004/article/details/79184230
@Conditional注解可以根据任何环境条件来注册bean,是否注册bean实例的条件有如下几个:
- 在classpath路径中是否存在某个特定的类
- 在ApplicationContext中是否还没注册过一个特定类型的bean
- 是否在某路径下存在某文件
- 是否在配置文件中配置了某特定的属性
- 是否存在某特定的系统环境变量
下面举些例子:
1、根据是否存在某特定的系统环境变量来注册bean
假如应用程序既可以使用mysql数据库,也可以使用Mongo数据库,需要根据系统环境变量dbType来使用不同的数据库。
为了利用spring boot的@Conditional注解决定实例化哪个数据源api,要实现判断条件:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MysqlDbTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
final String dbType = System.getProperty("dbType");
return "mysql".equalsIgnoreCase(dbType);
}
}
public class MongoDbTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
final String dbType = System.getProperty("dbType");
return "mongo".equalsIgnoreCase(dbType);
}
}
定义了两个Condition后,需要在对应的dao层注解:
public interface UserDao {
List<String> getAllUserNames();
}
@Repository
@Conditional(MongoDbTypeCondition.class)
public class MongoUserDaoImpl implements UserDao {
@Override
public List<String> getAllUserNames() {
return Arrays.asList("Mongo db", "test");
}
}
@Repository
@Conditional(MysqlDbTypeCondition.class)
public class MysqlUserDaoImpl implements UserDao {
@Override
public List<String> getAllUserNames() {
return Arrays.asList("mysql", "test");
}
}
之后,只需要在程序启动时,加上参数 -DdbType=xxx
来指定即可。
2、根据在classpath路径中是否存在某个特定的类来注册bean
还可以根据在classpath路径中是否存在某个特定的类来注册bean,还是由以上的代码来改动。假如,classpath路径下出现mysql驱动类com.mysql.jdbc.Driver
就实例化MysqlUserDaoImpl,否则就默认实例化MongoUserDaoImpl,现在只需要修改判断条件即可:
public class MysqlDbTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
public class MongoDbTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
return false;
} catch (ClassNotFoundException e) {
return true;
}
}
}
3、其它条件类推
想要实现其它的条件的推断,只需要按照需求实现Condition接口即可。
Spring boot提供的@Conditional注解有很多,例如**@ConditionalOnClass**、@ConditionalOnBean,按需取用。例如直接使用@ConditionalOnProperty(name = "dbType", havingValue = "mongo")
同样可以达到示例一实现的功能。
条件注解 @ConditionalOnBean 的正确使用姿势
在使用 @ConditionalOnBean 注解时会遇到不生效的情况,依赖的 bean 明明已经配置了,但就是不生效。是不是@ConditionalOnBean和 Bean加载的顺序有没有关系呢?问题演示:
@Configuration
public class Configuration1 {
@Bean
@ConditionalOnBean(Bean2.class)
public Bean1 bean1() {
return new Bean1();
}
}
@Configuration
public class Configuration2 {
@Bean
public Bean2 bean2(){
return new Bean2();
}
}
运行结果:@ConditionalOnBean(Bean2.class)返回false。明明定义的有bean2,bean1却未加载。
原因在于spring ioc的过程中,优先解析@Component,@Service,@Controller注解的类。其次解析配置类,也就是@Configuration标注的类。最后开始解析配置类中定义的bean。
示例代码中bean1是定义在配置类中的,当执行到配置类解析的时候,@Component,@Service,@Controller ,@Configuration标注的类已经全部扫描,所以这些BeanDifinition已经被同步。 但是bean1的条件注解依赖的是bean2,bean2是被定义的配置类中的,所以此时配置类的解析无法保证先后顺序,就会出现不生效的情况。
解决:项目中条件注解依赖的类,大多会交给spring容器管理,所以如果要在配置中Bean通过@ConditionalOnBean依赖配置中的Bean时,完全可以用@ConditionalOnClass(Bean2.class)来代替。