天天看点

SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition

    最近碰到个这样的需求,需要同一套代码适配个版本数据库(数据库不同,且部分表的字段及关联关系可能会不同),即这套代码配置不同的数据库都能跑。项目采用的框架为SpringBoot+Mybatis。经过一番思考,思路如下:

    (1)在业务层(service)和数据访问层(Mapper)之间添加一层适配层,用来屏蔽数据库的差异

    (2)适配层中代码均采用接口加实现类的方式,不同的数据库用的实现类不同

    (3)业务层(service)中全部采用面向接口编程

    (4)项目启动后只实例化和数据库相匹配的适配层实现类

    实现上面的一个关键点是对bean的实例化添加一个条件判断来控制。其实SpringBoot里面新增了很多条件注解,能实现这个功能。但是都有些局限性,最终是采用自定义条件注解的方案。

2.4.2.3.1)、通过SpringBoot自带的注解ConditionalOnProperty实现

        这个注解不做过多的解释,只说通过这个注解怎么实现我们的功能。

假设我们application.properties中配置一个配置项为

#bean实例化条件配置项
conditionKey: 1.0      

    那么只需要加上@ConditionalOnProperty的name和havingValue就能实现,只有配置文件中name对应的配置项的值和havingValue内容一致才实例化这个对象。

针对我们上面配置的application.properties的内容,@ConditionalOnProperty的使用案例如下面代码所示

ManageImpl1.java代码如下:(MyManage接口、ManageImpl2省略)

package com.dxz.palmpay.condition;

import javax.annotation.PostConstruct;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

//仅当conditionKey==1.0的时候实例化这个类
@ConditionalOnProperty(name = "conditionKey", havingValue = "1.0")
@Component
public class ManageImpl1 implements MyManage {

    @Override
    public void sayHello() {
        System.out.println("我是实现类01");
    }

    //为了效果,创建后打印一些信息
    @PostConstruct
    public void init() {
        this.sayHello();
    }

}      

 结果:

SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition

该配置放在配置中心同样有效。

SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition

    这个注解的局限性:这个注解的havingValue里面只能配置一个值。

    由于项目个性化需求,希望这个havingValue可以配置多个值,name对应的配置项的Value只要满足havingValue里面多个值的就表示匹配正确。即,havingValue里面可以配置多个值,name对应配置项的值来和havingValue匹配时,采用逻辑或匹配,满足一个值就算匹配正确。

2.4.2.3.2)、自定义条件注解

(1)思路

        注解里面有2个属性,具体如下

  • name:String类型,用来接受application.properties的配置项的key
  • havingValue:String数组类型,用来和name对应key的Value进行匹配

(2)定义注解

package com.dxz.palmpay.condition;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(CustomOnPropertyCondition.class)
public @interface CustomConditionalOnProperty {

    String name() default "";

    //havingValue数组,支持or匹配
    String[] havingValue() default {};

}      

(3)定义注解的匹配规则

package com.dxz.palmpay.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;

/**
 * 自定义条件注解的验证规则
 */
public class CustomOnPropertyCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Map<String, Object> annotationAttributes = annotatedTypeMetadata
                .getAnnotationAttributes(CustomConditionalOnProperty.class.getName());
        String propertyName = (String) annotationAttributes.get("name");
        String[] values = (String[]) annotationAttributes.get("havingValue");
        if (0 == values.length) {
            return false;
        }

        String propertyValue = conditionContext.getEnvironment().getProperty(propertyName);
        // 有一个匹配上就ok
        for (String havingValue : values) {
            if (propertyValue.equalsIgnoreCase(havingValue)) {
                return true;
            }
        }
        return false;
    }

}      

 (4)使用案例

@Component
@CustomConditionalOnProperty(name = "db.version", havingValue = {"3"})
public class ManageImpl3 implements MyManage {

@Component
@CustomConditionalOnProperty(name = "db.version", havingValue = {"1","2","4"})
public class ManageImpl4 implements MyManage {      

自定义Condition注解,主要就2步

(1)定义一个条件注解

(2)定义一个条件的校验规则

继续阅读