spring boot 自动配置解密之注解@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根据是否存在某特定的系统环境变量来注册bean

2、根据在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.java

3、其他条件类推

 其他条件:比如容器中是否存在某个类的实例或者类是否有特殊的注解等等,我们可以举一反三,因为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注解。




 具体实现的代码就不说了,感兴趣的看源码吧,原理是一样的。





猜你喜欢

转载自blog.csdn.net/doctor_who2004/article/details/79184230