在上一篇文章Java設計模式綜合運用(門面+模版方法+責任鏈+策略)中,筆者寫了一篇門面模式、模版方法、責任鏈跟策略模式的綜合運用的事例文章,但是后來筆者發(fā)現(xiàn),在實現(xiàn)策略模式的實現(xiàn)上,發(fā)現(xiàn)了一個弊端:那就是如果在后續(xù)業(yè)務發(fā)展中,需要再次增加一個業(yè)務策略的時候,則需要再次繼承
AbstractValidatorHandler類(詳情請參見上篇文章),這樣就會造成一定的類膨脹。今天我利用注解的方式改造成動態(tài)策略模式,這樣就只需要關注自己的業(yè)務類即可,無需再實現(xiàn)一個類似的Handler類。
1. 項目背景
1.1 項目簡介
在公司的一個業(yè)務系統(tǒng)中,有這樣的一個需求,就是根據(jù)不同的業(yè)務流程,可以根據(jù)不同的組合主鍵策略進行動態(tài)的數(shù)據(jù)業(yè)務查詢操作。在本文中,我假設有這樣兩種業(yè)務,客戶信息查詢和訂單信息查詢,對應以下枚舉類:
/**
* 業(yè)務流程枚舉
* @author landyl
* @create 11:18 AM 05/07/2018
*/
public enum WorkflowEnum {
ORDER(2),
CUSTOMER(3),
;
....
}
每種業(yè)務類型都有自己的組合主鍵查詢規(guī)則,并且有自己的查詢優(yōu)先級,比如客戶信息查詢有以下策略:
- customerId
- requestId
- birthDate+firstName
以上僅是假設性操作,實際業(yè)務規(guī)則比這復雜的多
1.2 流程梳理
主要業(yè)務流程,可以參照以下簡單的業(yè)務流程圖。
1.2.1 查詢抽象模型

1.2.2 組合主鍵查詢策略

1.2.3 組合主鍵查詢責任鏈

2. Java注解簡介
注解的語法比較簡單,除了@符號的使用之外,它基本與Java固有語法一致。
2.1 元注解
JDK1.5提供了4種標準元注解,專門負責新注解的創(chuàng)建。
| 注解 | 說明 |
|---|---|
| @Target | 表示該注解可以用于什么地方,可能的ElementType參數(shù)有: CONSTRUCTOR:構造器的聲明 FIELD:域聲明(包括enum實例) LOCAL_VARIABLE:局部變量聲明 METHOD:方法聲明 ACKAGE:包聲明 PARAMETER:參數(shù)聲明 TYPE:類、接口(包括注解類型)或enum聲明 |
| @Retention | 表示需要在什么級別保存該注解信息??蛇x的RetentionPolicy參數(shù)包括: SOURCE:注解將被編譯器丟棄 CLASS:注解在class文件中可用,但會被VM丟棄 RUNTIME:JVM將在運行期間保留注解,因此可以通過反射機制讀取注解的信息。 |
| @Document | 將注解包含在Javadoc中 |
| @Inherited | 允許子類繼承父類中的注解 |
2.2 自定義注解
定義一個注解的方式相當簡單,如下代碼所示:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
//使用@interface關鍵字定義注解
public @interface Description {
/*
* 注解方法的定義(其實在注解中也可以看做成員變量)有如下的規(guī)定:
* 1.不能有參數(shù)和拋出異常
* 2.方法返回類型只能為八種基本數(shù)據(jù)類型和字符串,枚舉和注解以及這些類型構成的數(shù)組
* 3.可以包含默認值,通過default實現(xiàn)
* 4.如果只有一個方法(成員變量),最好命名為value
*/
String value();
int count() default 1; //默認值為1
}
注解的可用的類型包括以下幾種:所有基本類型、String、Class、enum、Annotation、以上類型的數(shù)組形式。元素不能有不確定的值,即要么有默認值,要么在使用注解的時候提供元素的值。而且元素不能使用null作為默認值。注解在只有一個元素且該元素的名稱是value的情況下,在使用注解的時候可以省略“value=”,直接寫需要的值即可。
2.3 使用注解
如上所示的注解使用如下:
/**
* @author landyl
* @create 2018-01-12:39 PM
*/
//在類上使用定義的Description注解
@Description(value="class annotation",count=2)
public class Person {
private String name;
private int age;
//在方法上使用定義的Description注解
@Description(value="method annotation",count=3)
public String speak() {
return "speaking...";
}
}
使用注解最主要的部分在于對注解的處理,那么就會涉及到注解處理器。從原理上講,注解處理器就是通過反射機制獲取被檢查方法上的注解信息,然后根據(jù)注解元素的值進行特定的處理。
/**
* @author landyl
* @create 2018-01-12:35 PM
* 注解解析類
*/
public class ParseAnnotation {
public static void main(String[] args){
//使用類加載器加載類
try {
Class c = Class.forName("com.annatation.Person");//加載使用了定義注解的類
//找到類上的注解
boolean isExist = c.isAnnotationPresent(Description.class);
if(isExist){
//拿到注解示例
Description d = (Description)c.getAnnotation(Description.class);
System.out.println(d.value());
}
//找到方法上的注解
Method[] ms = c.getMethods();
for(Method m : ms){
boolean isMExist = m.isAnnotationPresent(Description.class);
if(isMExist){
Description d = m.getAnnotation(Description.class);
System.out.println(d.value());
}
}
//另外一種注解方式
for(Method m:ms){
Annotation[] as = m.getAnnotations();
for(Annotation a:as){
if(a instanceof Description){
Description d = (Description)a;
System.out.println(d.value());
}
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3. 策略模式升級版
3.1 策略模式實現(xiàn)方式
- 使用工廠進行簡單的封裝
- 使用注解動態(tài)配置策略
- 使用模版方法模式配置策略(參見Java設計模式綜合運用(門面+模版方法+責任鏈+策略))
- 使用工廠+注解方式動態(tài)配置策略(利用Spring加載)
其中第1、2點請參見org.landy.strategy 包下的demo事例即可,而第4點的方式其實就是結合第1、2、3點的優(yōu)點進行整合的方式。
3.2 注解方式優(yōu)點
使用注解方式可以極大的減少使用模版方法模式帶來的擴展時需要繼承模版類的弊端,工廠+注解的方式可以無需關心其他業(yè)務類的實現(xiàn),而且減少了類膨脹的風險。
3.3 組合主鍵查詢策略
本文以組合主鍵查詢策略這一策略進行說明,策略注解如下:
/**
* 組合主鍵查詢策略(根據(jù)不同業(yè)務流程區(qū)分組合主鍵查詢策略,并且每個業(yè)務流程都有自己的優(yōu)先級策略)
* @author landyl
* @create 2:22 PM 09/29/2018
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface KeyIdentificationStrategy {
/**
* 主鍵策略優(yōu)先級
* @return
*/
int priority() default 0;
/**
* 業(yè)務流程類型(如:訂單信息,會員信息等業(yè)務流程)
* @return
*/
WorkflowEnum workflowId();
/**
* the spring bean name
* @return
*/
String beanName();
}
3.4 策略工廠
既然定義了組合主鍵查詢策略注解,那必然需要一個注解處理器進行解析注解的操作,本文以工廠的方式進行。主要邏輯如下:
-
掃描指定包下的Java類,找出相應接口(即
KeyIdentification)下的所有Class對象。private List<Class<? extends KeyIdentification>> getIdentifications() { Set<String> packageNames = this.getBasePackages(); List<Class<? extends KeyIdentification>> identifications = new ArrayList<>(); if(packageNames != null) { packageNames.forEach((packageName) -> identifications.addAll(getIdentifications(packageName))); } return identifications; } -
解析注解
KeyIdentificationStrategy,定義一個排序對象(KeyIdentificationComparator),指定優(yōu)先級。/** * define a comparator of the KeyIdentification object through the priority of the IdentifyPriority for sort purpose. * @see KeyIdentificationStrategy */ private class KeyIdentificationComparator implements Comparator { @Override public int compare(Object objClass1, Object objClass2) { if(objClass1 != null && objClass2 != null) { Optional<KeyIdentificationStrategy> strategyOptional1 = getPrimaryKeyIdentificationStrategy((Class)objClass1); Optional<KeyIdentificationStrategy> strategyOptional2 = getPrimaryKeyIdentificationStrategy((Class)objClass2); KeyIdentificationStrategy ip1 = strategyOptional1.get(); KeyIdentificationStrategy ip2 = strategyOptional2.get(); Integer priority1 = ip1.priority(); Integer priority2 = ip2.priority(); WorkflowEnum workflow1 = ip1.workflowId(); WorkflowEnum workflow2 = ip2.workflowId(); //先按業(yè)務類型排序 int result = workflow1.getValue() - workflow2.getValue(); //再按優(yōu)先級排序 if(result == 0) return priority1.compareTo(priority2); return result; } return 0; } } -
根據(jù)注解,把相應業(yè)務類型的組合主鍵查詢策略對象放入容器中(即
DefaultKeyIdentificationChain)。KeyIdentificationStrategy strategy = strategyOptional.get(); String beanName = strategy.beanName(); //業(yè)務流程類型 WorkflowEnum workflowId = strategy.workflowId(); KeyIdentificationStrategy priority = getPrimaryKeyIdentificationStrategy(v).get(); LOGGER.info("To add identification:{},spring bean name is:{},the identify priority is:{},workflowId:{}",simpleName,beanName,priority.priority(),workflowId.name()); KeyIdentification instance = ApplicationUtil.getApplicationContext().getBean(beanName,v); defaultKeyIdentificationChain.addIdentification(instance,workflowId); 后續(xù),在各自對應的業(yè)務查詢組件對象中即可使用該工廠對象調用如下方法,即可進行相應的查詢操作。
public IdentificationResultType identify(IdentifyCriterion identifyCriterion,WorkflowEnum workflowId) {
//must set the current workflowId
defaultKeyIdentificationChain.doClearIdentificationIndex(workflowId);
return defaultKeyIdentificationChain.doIdentify(identifyCriterion,workflowId);
}
4. 總結
以上就是本人在實際工作中,對第一階段使用到的設計模式的一種反思后得到的優(yōu)化結果,可能還有各種不足,但是個人感覺還是有改進,希望大家也不吝賜教,大家一起進步才是真理。
代碼地址:https://github.com/landy8530/DesignPatterns (歡迎Star)