共计 6961 个字符,预计需要花费 18 分钟才能阅读完成。
业务场景
在与仓库系统的对接过程中,我们使用了阿里巴巴的奇门规范。该规范中根据不同的 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<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 来找到具体的业务逻辑代码。如果有不明白的地方可以在下面留言。