spring實戰:使用註解+反射來解決switch或者if多條件判斷的問題

業務場景

在與倉庫系統的對接過程中,我們使用了阿里巴巴的奇門規範。該規範中根據不同的method方法參數來確定不同的業務,比如:

# 入庫單創建
method=taobao.qimen.entryorder.create
# 庫存查詢
method=taobao.qimen.inventory.query
# 商品同步接口
method=taobao.qimen.singleitem.synchronize

那麼我們在解析的時候,常用的方式就是使用switch或者if來處理,以switch爲例,實現代碼如下:

switch (method) {
    case "taobao.qimen.entryorder.create":
        return entryorderCreate();
    case ""taobao.qimen.inventory.query:
        return inventoryQuery();
    case "taobao.qimen.singleitem.synchronize":
        return singleitemSyncronize();
    default:
        return "";
}

通過switch,我們根據不同的method能夠返回不同的執行邏輯結果。從功能上來說,沒有任何的毛病。但是作爲一個程序員,如果只是爲了完成功能而寫代碼,那這又的程序員是沒有靈魂的。

問題

在奇門api技術文檔中,大概有50多個不同的業務接口method,這也就意味着我們至少要case 50次以上。你覺得一個switch中case 50次合理嗎?答案當然是不合理的。

在這了再分享一句話:

任何一個傻瓜都能寫出計算機能理解的程序,而優秀的程序員卻能寫出別人能讀得懂的程序。—— Martin Fowler

解決方案

解決思路
每次接受請求之後,根據method的不同,來執行不同的業務邏輯。那麼我們能不能將請求的method和需要執行的業務邏輯方法做一個映射,這樣我們根據method就能直接找到具體的業務邏輯處理方法。
clipboard.png
那麼我們的method怎麼和我們的業務方法映射綁定呢?解決方法是在每個業務方法上面增加一個註解(比如@Name)。那麼問題來了,我們什麼時候生成這樣的映射關係呢?
我們可以在容器啓動的時候,就去生成這樣的映射關係。那麼我們怎麼知道哪些類包含了具有@Name註解的方法呢?爲了能快速獲取到包含@Name的類,我們增加一個類註解@MethodHandler,在方法上使用了@Name註解的類上我們加上一個@MethodHandler註解。這樣我們就能快速找到這樣的類了。

具體實現

@Name註解

@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
    String[] value() default {};
}

@Target(ElementType.METHOD)表示@Name是個方法註解。同時裏面的value是個數組,是因爲可能存在多個method執行相同業務邏輯的情況

@MethodHandler註解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodHandler {

}

@Target({ElementType.TYPE})表示@MethodHandler是個類或者接口註解,次註解的作用是讓我們能快速找到包含@Name註解的方法。

MethodMappering

/**
 * 方法映射
 */
public class MethodMapping {
    //方法註解對應的名字
    public String[] names;
    //具體的執行方法
    public Method method;
    public MethodMapping(String[] names, Method method) {
        this.names = names;
        this.method = method;
    }
}

這個類主要存儲奇門method和具體執行的方法的映射

MethodNames

public class MethodNames {
    public static final String deliveryorder_confirm = "deliveryorder.confirm";
    public static final String taobao_qimen_deliveryorder_confirm = "taobao.qimen.deliveryorder.confirm";
    public static final String deliveryorder_batchconfirm = "deliveryorder.batchconfirm";
    public static final String taobao_qimen_deliveryorder_batchconfirm = "taobao.qimen.deliveryorder.batchconfirm";
    public static final String stockchange_report = "stockchange.report";
    public static final String taobao_qimen_stockchange_report = "taobao.qimen.stockchange.report";
    public static final String stockout_confirm = "stockout.confirm";
    public static final String taobao_qimen_stockout_confirm = "taobao.qimen.stockout.confirm";
    public static final String entryorder_confirm = "entryorder.confirm";
    public static final String taobao_qimen_entryorder_confirm = "taobao.qimen.entryorder.confirm";
    public static final String itemlack_report = "itemlack.report";
    public static final String taobao_qimen_itemlack_report = "taobao.qimen.itemlack.report";
    public static final String orderprocess_report = "orderprocess.report";
    public static final String taobao_qimen_orderprocess_report = "taobao.qimen.orderprocess.report";
    public static final String returnorder_confirm = "returnorder.confirm";
    public static final String taobao_qimen_returnorder_confirm = "taobao.qimen.returnorder.confirm";
    public static final String returnapply_report = "returnapply.report";
    public static final String taobao_qimen_returnapply_report = "taobao.qimen.returnapply.report";
    public static final String qimen_taobao_qianniu_cloudkefu_address_self_modify = "qimen.taobao.qianniu.cloudkefu.address.self.modify";
}

MethodNames類主要記錄了奇門中所有的method(此處只展示部分)

註解解析和檢查類DetectMethodAnnotation

@Component
public class DetectMethodAnnotation extends AbstractReturner implements ApplicationContextAware, InitializingBean {

    private ApplicationContext applicationContext;

    //存儲類-方法
    private HashMap<String, List<MethodMapping>> classMethodMap = new HashMap<>();

    /**
     * 初始化容器後解析所有包含MethodHandler註解的類中包含Name註解的方法
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        //獲取包含註解MethodHandler的類
        Map<String, Object> methodHandlerMap = applicationContext.getBeansWithAnnotation(MethodHandler.class);
        methodHandlerMap.forEach((k, v) -> {
            Class<?> clazz = v.getClass();
            //獲取所有的方法(不包括繼承的方法)
            Method[] methods = clazz.getDeclaredMethods();
            List<MethodMapping> methodMappings = new ArrayList<>();
            for (Method method : methods) {
                //只解析@Name註解的,並且返回值爲Returner的方法,方便對結果進行解析
                if (method.isAnnotationPresent(Name.class) && (method.getReturnType() == Returner.class)) {
                    Name nameAnnotation = method.getAnnotation(Name.class);
                    methodMappings.add(new MethodMapping(nameAnnotation.value(), method));
                }
            }
            if (!methodMappings.isEmpty()) {
                classMethodMap.put(clazz.getName(), methodMappings);
            }
        });
    }

    /**
     * 執行
     *
     * @param name
     * @return
     */
    public <T> Returner<T> execute(String name, Object... parameters) throws Exception {
        if (!classMethodMap.containsKey(this.getClass().getName())) {
            return fail("類[" + this.getClass().getName() + "]未使用註解@MethodHandler註冊或未發現任何使用@Name註解的非繼承方法");
        }

        List<MethodMapping> methodMappings = classMethodMap.get(this.getClass().getName());
        for (MethodMapping methodMapping : methodMappings) {
            String[] names = methodMapping.names;
            if (Arrays.asList(names).contains(name)) {
                return (Returner) methodMapping.method.invoke(this, parameters);
            }
        }
        return fail("未發現使用註解 @Name(\"" + name + "\") 爲的方法");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

DetectMethodAnnotation的作用如下:

  • 實現ApplicationContextAware接口,這樣能獲取到上下文對象ApplicationContext
  • 實現InitializingBean接口的afterPropertiesSet()方法,此方法在容器啓動之後只執行一次,在此方法中可以解析所有的@Name註解
  • 解析的數據存放在classMethodMap中,classMethodMap的數據結構Hahs<className,List<method和業務邏輯映射關係對象>>
  • 提供一個方法execute,外部只需要傳遞method和業務邏輯方法的參數即可。

QimenController

@Controller
@MethodHandler
public class QimenController extends DetectMethodAnnotation {
    @Name({MethodNames.deliveryorder_confirm, MethodNames.taobao_qimen_deliveryorder_confirm})
    public Returner<String> deliveryorderConfirm(String deliveryOrderCode) {
        logger.info("execute deliveryorderConfirm method with value " + deliveryOrderCode);
        return success("");
    }

    @Name(MethodNames.stockchange_report)
    public Returner<String> stockchangeReport() {
        return success("");
    }
}

通過QimenController的接口可以看到具體的使用方式,類上面使用@MethodHandler註解,方法上使用@Name註解,@Name註解中傳入MethodNames類中定義的名字即可。

測試

public class Run {
    public static final Logger logger = LoggerFactory.getLogger(Run.class);
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Run.class);
        QimenController qimenController = applicationContext.getBean(QimenController.class);
        Returner<String> execute = qimenController.execute(MethodNames.deliveryorder_confirm, "T123456789");
        logger.info("deliveryorder_confirm:{}", execute);
        logger.info("stockchange_report:{}", qimenController.execute(MethodNames.stockchange_report));
        applicationContext.close();
    }
}

執行結果如下

[main] INFO  solution.swithCase.QimenController - [18] - execute deliveryorderConfirm method with value T123456789
[main] INFO  solution.swithCase.Run - [29] - deliveryorder_confirm:Returner(code=0, desc=null, body=)
[main] INFO  solution.swithCase.Run - [30] - stockchange_report:Returner(code=0, desc=null, body=)

Returner對象

@Data
public class Returner<T> implements Serializable {
    private String code;
    private String desc;
    private T body;
}
此對象主要爲了統一返回值,方便解析

總結

首先要先明白解決方案思路才能理解代碼,其實就是把類-method-業務邏輯做一個映射,這樣就能直接通過接口中傳遞的method來找到具體的業務邏輯代碼。如果有不明白的地方可以在下面留言。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章