conditional
spring boot 自动配置解密之注解@Conditional
在开发基于spring的应用程序时,我们可能需要根据环境条件注册不同的bean实例。比如常见的数据库的数据源的配置,日常环境、测试环境及线上环境,所连接的数据库地址及相关配置是不一样的(其实可以利用不同的环境对应的域名也
可解决,我们这里不讨论先)。
为了解决这个问题,spring3引入了profiles的概念,详细请参考官方文档。为了更能灵活的根据环境条件注册bean实例,
spring4又引入了@Conditional注解。有个@Conditional注解,我们可以根据任何环境条件来注册bean,
比如是否注册bean实例的条件:
- 在classpath路径中是否存在某个特定的类
- 在ApplicationContext中是否还没注册过一个特定类型的bean
- 是否在某路径下存在某文件
- 是否在配置文件中配置了某特定的属性
- 是否存在某特定的系统环境变量
下面举些例子:
1、根据是否存在某特定的系统环境变量来注册bean
假如我们的应用程序既可以使用mysql数据库,也可以使用Mongo 数据库,我们需要根据系统环境变量dbType来使用不同的数据库,dbType 为mysql,程序使用mysql提供的api实现提供数据,dbType为mongo,程序使用Mongo提供的api实现提供数据。
我们首先定义公共接口:
import java.util.List;
/**
* @author sdcuike
* @date 2018/1/28
* @since 2018/1/28
*/
public interface UserDao {
List<String> getAllUserNames();
}
mysql数据源api的实现:package com.sdcuike.springboot.practice.conditional.demo.properties;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Repository;
import java.util.Arrays;
import java.util.List;
/**
* @author sdcuike
* @date 2018/1/28
* @since 2018/1/28
*/
@Repository
public class JdbcUserDaoImpl implements UserDao {
@Override
public List<String> getAllUserNames() {
return Arrays.asList("jdbc", "test");
}
}
mongo数据源api的实现:
package com.sdcuike.springboot.practice.conditional.demo.properties;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Repository;
import java.util.Arrays;
import java.util.List;
/**
* @author sdcuike
* @date 2018/1/28
* @since 2018/1/28
*/
@Repository
public class MongoUserDaoImpl implements UserDao {
@Override
public List<String> getAllUserNames() {
return Arrays.asList(" Mongo db ", "test");
}
}
为了利用spring boot的@Conditional注解决定实例化哪个数据源api,我们还要实现判断条件:package com.sdcuike.springboot.practice.conditional.demo.properties;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author sdcuike
* @date 2018/1/28
* @since 2018/1/28
*/
public class MySqlDbTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
final String dbType = System.getProperty("dbType");
return "mysql".equalsIgnoreCase(dbType);
}
}
package com.sdcuike.springboot.practice.conditional.demo.properties;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author sdcuike
* @date 2018/1/28
* @since 2018/1/28
*/
public class MongoDbTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
final String dbType = System.getProperty("dbType");
return "mongo".equalsIgnoreCase(dbType);
}
}
定义了两个Condition,我们还需要改变JdbcUserDaoImpl、MongoUserDaoImpl实例化的条件:
即加上注解@Conditional(MongoDbTypeCondition.class)、@Conditional(MySqlDbTypeCondition.class):
@Repository
@Conditional(MySqlDbTypeCondition.class)
public class JdbcUserDaoImpl implements UserDao {
@Override
public List<String> getAllUserNames() {
return Arrays.asList("jdbc", "test");
}
}
@Repository
@Conditional(MongoDbTypeCondition.class)
public class MongoUserDaoImpl implements UserDao {
@Override
public List<String> getAllUserNames() {
return Arrays.asList(" Mongo db ", "test");
}
}
测试用例:package com.sdcuike.springboot.practice.conditional.demo.properties;
import com.sdcuike.springboot.practice.SpringApplicationBoot;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Arrays;
import java.util.List;
/**
* @author sdcuike
* @date 2018/1/28
* @since 2018/1/28
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringApplicationBoot.class)
public class UserDaoTest {
@Autowired
private UserDao userDao;
@BeforeClass
public static void init() {
System.setProperty("dbType", "mongo");
//System.setProperty("dbType", "mysql");
}
@AfterClass
public static void close() {
System.clearProperty("dbType");
}
@Test
public void testMysqlDbType() {
System.out.println("=====================");
final List<String> allUserNames = userDao.getAllUserNames();
Assert.assertEquals(Arrays.asList(" Mongo db ", "test"), allUserNames);
System.out.println("=====================");
}
}
代码详见:https://github.com/sdcuike/spring-boot-practice/tree/blog2018年01月28根据是否存在某特定的系统环境变量来注册bean2、根据在classpath路径中是否存在某个特定的类来注册bean
现在我们来看一下根据在classpath路径中是否存在某个特定的类来注册bean,还是由以上的代码来改动,假如,
classpath路径下出现mysql驱动类com.mysql.jdbc.Driver就实例化JdbcUserDaoImpl,
否则就默认实例化MongoUserDaoImpl,现在只需要增加改动判断条件即可:
public class MySqlDriverPresentsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
Class.forName("com.mysql.jdbc.Driver");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
public class MySqlDriverNotPresentsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
Class.forName("com.mysql.jdbc.Driver");
return false;
} catch (ClassNotFoundException e) {
return true;
}
}
}
同时修改:
@Repository
@Conditional(MySqlDriverPresentsCondition.class)
public class JdbcUserDaoImpl implements UserDao {
@Override
public List<String> getAllUserNames() {
return Arrays.asList("jdbc", "test");
}
}
@Repository
@Conditional(MySqlDriverNotPresentsCondition.class)
public class MongoUserDaoImpl implements UserDao {
@Override
public List<String> getAllUserNames() {
return Arrays.asList(" Mongo db ", "test");
}
}
代码及测试用例见:https://github.com/sdcuike/spring-boot-practice/blob/blog2018年01月28根据在classpath路径中是否存在某个特定的类来注册bean/src/test/java/com/sdcuike/springboot/practice/conditional/demo/properties/UserDaoTest.java3、其他条件类推
其他条件:比如容器中是否存在某个类的实例或者类是否有特殊的注解等等,我们可以举一反三,因为spring 提供的接口:
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
方法 boolean matches(ConditionContext context, AnnotatedTypeMetadata
metadata)里面的参数ConditionContext及AnnotatedTypeMetadata很容器获取这些信息。4、spring boot提供的@Conditional注解
其实spring 提倡注解,所以spring boot也为我们提供了很多常见@Conditional注解。
具体实现的代码就不说了,感兴趣的看源码吧,原理是一样的。
相关阅读
springboot 2.0 使用Hikari连接池(号称java平台最快的,
1.springboot 2.0 默认连接池就是Hikari了,所以引用parents后不用专门加依赖 2.贴我自己的配置(时间单位都是毫秒) # jdbc_config
一、对AOP的初印象 首先先给出一段比较专业的术语(来自百度): 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通
@Query与@Modifying执行更新操作@Query 与 @Modifying这两个annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最
spring Quartz 源码分析--触发器类CronTriggerBean源
前面我们讲到了Quartz框架在项目中的实现,在Quartz中的重要API有两个重要的触发器类:CronTrigger 和SimpleTrigger 在Quartz框
文章开头先引入一处图片。这处图片引自老罗的博客。为了避免不必要的麻烦,首先声明我个人比较尊敬老罗的。至于为什么放这张图,自然