最近碰到個這樣的需求,需要同一套代碼適配個版本數據庫(數據庫不同,且部分表的字段及關聯關系可能會不同),即這套代碼配置不同的數據庫都能跑。項目采用的框架為SpringBoot+Mybatis。經過一番思考,思路如下:
(1)在業務層(service)和數據訪問層(Mapper)之間添加一層適配層,用來屏蔽數據庫的差異
(2)
適配層中代碼均采用接口加實現類的方式,不同的數據庫用的實現類不同
(3)業務層(service)中全部采用面向接口編程
(4)項目啟動后只實例化和數據庫相匹配的適配層實現類
實現上面的一個關鍵點是對bean的實例化添加一個條件判斷來控制。其實SpringBoot里面新增了很多條件注解,能實現這個功能。但是都有些局限性,最終是采用自定義條件注解的方案。
一、SpringBoot自帶的注解ConditionalOnProperty
這個注解不做過多的解釋,只說通過這個注解怎么實現我們的功能。
假設我們application.properties中配置一個配置項為
#bean實例化條件配置項
conditionKey=1.0
2
1
#bean實例化條件配置項
2
conditionKey=1.0
那么只需要加上@ConditionalOnProperty的name和havingValue就能實現,只有配置文件中name對應的配置項的值和havingValue內容一致才實例化這個對象。
針對我們上面配置的application.properties的內容,@ConditionalOnProperty的使用案例如下面代碼所示
// 僅當conditionKey==1.0的時候實例化這個類
@Component
@ConditionalOnProperty(name = "conditionKey", havingValue = "1.0")
public class Manage1Impl implements MyManage{
@Override
public void sayHello() {
System.out.println("我是實現類01");
}
@PostConstruct
public void init() {
this.sayHello();
}
}
15
1
// 僅當conditionKey==1.0的時候實例化這個類
2
3
name = "conditionKey", havingValue = "1.0") (
4
public class Manage1Impl implements MyManage{
5
6
7
public void sayHello() {
8
System.out.println("我是實現類01");
9
}
10
11
12
public void init() {
13
this.sayHello();
14
}
15
}
這個注解的局限性:
這個注解的havingValue里面只能配置一個值。
由於項目個性化需求,希望這個havingValue可以配置多個值,
name對應的配置項的Value只要滿足havingValue里面多個值的就表示匹配正確。即,havingValue里面可以配置多個值,name對應配置項的值來和havingValue匹配時,采用邏輯或匹配,滿足一個值就算匹配正確。
二、自定義條件注解
(1)思路
注解里面有2個屬性,具體如下
- name:String類型,用來接受application.properties的配置項的key
- havingValue:String數組類型,用來和name對應key的Value進行匹配
(2)定義注解
package com.zxy.config;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
/**
* 自定義條件注解
* @author ZENG.XIAO.YAN
* @version 1.0
* @Date 2019-04-15
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(CustomOnPropertyCondition.class)
public @interface CustomConditionalOnProperty {
/**
* 條件變量的name
*/
String name() default "";
/**
* havingValue數組,支持or匹配
*/
String[] havingValue() default {};
}
28
1
package com.zxy.config;
2
3
import org.springframework.context.annotation.Conditional;
4
import java.lang.annotation.*;
5
/**
6
* 自定義條件注解
7
* @author ZENG.XIAO.YAN
8
* @version 1.0
9
* @Date 2019-04-15
10
*/
11
RetentionPolicy.RUNTIME) (
12
ElementType.TYPE, ElementType.METHOD}) ({
13
14
CustomOnPropertyCondition.class) (
15
public @interface CustomConditionalOnProperty {
16
17
/**
18
* 條件變量的name
19
*/
20
String name() default "";
21
22
/**
23
* havingValue數組,支持or匹配
24
*/
25
String[] havingValue() default {};
26
27
}
28
(3)定義注解的匹配規則
package com.zxy.config;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
/**
* 自定義條件注解的驗證規則
* @author ZENG.XIAO.YAN
* @version 1.0
* @Date 2019-04-15
*/
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;
}
}
x
1
package com.zxy.config;
2
3
import org.springframework.context.annotation.Condition;
4
import org.springframework.context.annotation.ConditionContext;
5
import org.springframework.core.type.AnnotatedTypeMetadata;
6
import java.util.Map;
7
8
/**
9
* 自定義條件注解的驗證規則
10
* @author ZENG.XIAO.YAN
11
* @version 1.0
12
* @Date 2019-04-15
13
*/
14
public class CustomOnPropertyCondition implements Condition {
15
16
17
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
18
Map<String, Object> annotationAttributes = annotatedTypeMetadata.getAnnotationAttributes(CustomConditionalOnProperty.class.getName());
19
String propertyName = (String) annotationAttributes.get("name");
20
String[] values = (String[]) annotationAttributes.get("havingValue");
21
if (0 == values.length) {
22
return false;
23
}
24
String propertyValue = conditionContext.getEnvironment().getProperty(propertyName);
25
// 有一個匹配上就ok
26
for (String havingValue : values) {
27
if (propertyValue.equalsIgnoreCase(havingValue)) {
28
return true;
29
}
30
}
31
return false;
32
}
33
}
34
(4)使用案例
直接參考下面2圖吧


三、小結
自定義Condition注解,主要就2步
(1)定義一個條件注解(2)定義一個條件的校驗規則