资讯专栏INFORMATION COLUMN

spring实战:使用注解+反射来解决switch或者if多条件判断的问题

Jason / 1735人阅读

摘要:从功能上来说,没有任何的毛病。解决方案每次接受请求之后,根据的不同,来执行不同的业务逻辑。具体实现注解表示是个方法注解。通过的接口可以看到具体的使用方式,类上面使用注解,方法上使用注解,注解中传入类中定义的名字即可。

业务场景

在与仓库系统的对接过程中,我们使用了阿里巴巴的奇门规范。该规范中根据不同的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就能直接找到具体的业务逻辑处理方法。

那么我们的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> classMethodMap = new HashMap<>();

    /**
     * 初始化容器后解析所有包含MethodHandler注解的类中包含Name注解的方法
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        //获取包含注解MethodHandler的类
        Map methodHandlerMap = applicationContext.getBeansWithAnnotation(MethodHandler.class);
        methodHandlerMap.forEach((k, v) -> {
            Class clazz = v.getClass();
            Method[] methods = clazz.getDeclaredMethods();//获取所有的方法
            List 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  Returner execute(String name, Object... parameters) throws Exception {
        if (!classMethodMap.containsKey(this.getClass().getName())) {
            return fail("类[" + this.getClass().getName() + "]未使用注解@MethodHandler注册或未发现任何使用@Name注解的非继承方法");
        }

        List 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的数据结构Hash<类名,List>

提供一个方法execute,外部只需要传递method和业务逻辑方法的参数即可。

QimenController

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

    @Name(MethodNames.stockchange_report)
    public Returner 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 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 implements Serializable {
    private String code;
    private String desc;
    private T body;
}

此对象主要为了统一返回值,方便解析

总结
首先要先明白解决方案思路才能理解代码,其实就是把类-method-业务逻辑做一个映射,这样就能直接通过接口中传递的method来找到具体的业务逻辑代码。如果有不明白的地方可以在下面留言。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75268.html

相关文章

  • Javag工程师成神之路(2019正式版)

    摘要:结构型模式适配器模式桥接模式装饰模式组合模式外观模式享元模式代理模式。行为型模式模版方法模式命令模式迭代器模式观察者模式中介者模式备忘录模式解释器模式模式状态模式策略模式职责链模式责任链模式访问者模式。 主要版本 更新时间 备注 v1.0 2015-08-01 首次发布 v1.1 2018-03-12 增加新技术知识、完善知识体系 v2.0 2019-02-19 结构...

    Olivia 评论0 收藏0
  • 《 Kotlin + Spring Boot : 下一代 Java 服务端开发 》

    摘要:下一代服务端开发下一代服务端开发第部门快速开始第章快速开始环境准备,,快速上手实现一个第章企业级服务开发从到语言的缺点发展历程的缺点为什么是产生的背景解决了哪些问题为什么是的发展历程容器的配置地狱是什么从到下一代企业级服务开发在移动开发领域 《 Kotlin + Spring Boot : 下一代 Java 服务端开发 》 Kotlin + Spring Boot : 下一代 Java...

    springDevBird 评论0 收藏0
  • Java深入-框架技巧

    摘要:从使用到原理学习线程池关于线程池的使用,及原理分析分析角度新颖面向切面编程的基本用法基于注解的实现在软件开发中,分散于应用中多出的功能被称为横切关注点如事务安全缓存等。 Java 程序媛手把手教你设计模式中的撩妹神技 -- 上篇 遇一人白首,择一城终老,是多么美好的人生境界,她和他历经风雨慢慢变老,回首走过的点点滴滴,依然清楚的记得当初爱情萌芽的模样…… Java 进阶面试问题列表 -...

    chengtao1633 评论0 收藏0
  • Spring IOC知识点一网打尽!

    摘要:使用的好处知乎的回答不用自己组装,拿来就用。统一配置,便于修改。 前言 只有光头才能变强 回顾前面: 给女朋友讲解什么是代理模式 包装模式就是这么简单啦 单例模式你会几种写法? 工厂模式理解了没有? 在刷Spring书籍的时候花了点时间去学习了单例模式和工厂模式,总的来说还是非常值得的! 本来想的是刷完《Spring 实战 (第4版)》和《精通Spring4.x 企业应用开发实战》...

    djfml 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<