1. 案例分析

1.1 案例场景

假设银行提供了一些 API 接口,对参数的序列化有点特殊,不使用 JSON,而是需要我们把参数依次拼在一起构成一个大字符串

按照银行提供的API文档顺序,将所有的参数构成定长的数据,并且拼接在一起作为一整个字符串

因为每一种参数都有固定长度,未达到长度需要进行填充处理

字符串类型参数不满长度部分要以下划线右填充,即字符串内容靠左

数字类型的参数不满长度部分以0左填充,即实际数字靠右

货币类型的表示需要把金额向下舍入2位到分,以分为单位,作为数字类型同样进行左填充

参数做MD5 操作作为签名

1.2 初步代码实现

public class BankService {

//创建用户方法

public static String createUser(String name, String identity, String mobile, int age) throws IOException {

StringBuilder stringBuilder = new StringBuilder();

//字符串靠左,多余的地方填充_

stringBuilder.append(String.format("%-10s", name).replace(' ', '_'));

//字符串靠左,多余的地方填充_

stringBuilder.append(String.format("%-18s", identity).replace(' ', '_'));

//数字靠右,多余的地方用0填充

stringBuilder.append(String.format("%05d", age));

//字符串靠左,多余的地方用_填充

stringBuilder.append(String.format("%-11s", mobile).replace(' ', '_'));

//最后加上MD5作为签名

stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));

return Request.Post("http://localhost:45678/reflection/bank/createUser")

.bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON)

.execute().returnContent().asString();

}

//支付方法

public static String pay(long userId, BigDecimal amount) throws IOException {

StringBuilder stringBuilder = new StringBuilder();

//数字靠右,多余的地方用0填充

stringBuilder.append(String.format("%020d", userId));

//金额向下舍入2位到分,以分为单位,作为数字靠右,多余的地方用0填充

stringBuilder.append(String.format("%010d", amount.setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue()));

//最后加上MD5作为签名

stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));

return Request.Post("http://localhost:45678/reflection/bank/pay")

.bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON)

.execute().returnContent().asString();

}

}

这样做能够基本满足需求,但是存在一些问题:

处理逻辑互相之间有重复,稍有不慎就会出现Bug

处理流程中字符串拼接、加签和发请求的逻辑,在所有方法重复

实际方法的入参的参数类型和顺序,不一定和接口要求一致,容易出错

代码层面参数硬编码,无法清晰进行核对

1.3 使用接口和反射优化代码

1.3.1 实现定义了所有接口参数的POJO类

@Data

public class CreateUserAPI {

private String name;

private String identity;

private String mobile;

private int age;

}

1.3.2 定义注解本身

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Documented

@Inherited

public @interface BankAPI {

String desc() default "";

String url() default "";

}

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.FIELD)

@Documented

@Inherited

public @interface BankAPIField {

int order() default -1;

int length() default -1;

String type() default "";

}

1.3.3 反射配合注解实现动态的接口参数组装

private static String remoteCall(AbstractAPI api) throws IOException {

//从BankAPI注解获取请求地址

BankAPI bankAPI = api.getClass().getAnnotation(BankAPI.class);

bankAPI.url();

StringBuilder stringBuilder = new StringBuilder();

Arrays.stream(api.getClass().getDeclaredFields()) //获得所有字段

.filter(field -> field.isAnnotationPresent(BankAPIField.class)) //查找标记了注解的字段

.sorted(Comparator.comparingInt(a -> a.getAnnotation(BankAPIField.class).order())) //根据注解中的order对字段排序

.peek(field -> field.setAccessible(true)) //设置可以访问私有字段

.forEach(field -> {

//获得注解

BankAPIField bankAPIField = field.getAnnotation(BankAPIField.class);

Object value = "";

try {

//反射获取字段值

value = field.get(api);

} catch (IllegalAccessException e) {

e.printStackTrace();

}

//根据字段类型以正确的填充方式格式化字符串

switch (bankAPIField.type()) {

case "S": {

stringBuilder.append(String.format("%-" + bankAPIField.length() + "s", value.toString()).replace(' ', '_'));

break;

}

case "N": {

stringBuilder.append(String.format("%" + bankAPIField.length() + "s", value.toString()).replace(' ', '0'));

break;

}

case "M": {

if (!(value instanceof BigDecimal))

throw new RuntimeException(String.format("{} 的 {} 必须是BigDecimal", api, field));

stringBuilder.append(String.format("%0" + bankAPIField.length() + "d", ((BigDecimal) value).setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue()));

break;

}

default:

break;

}

});

//签名逻辑

stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));

String param = stringBuilder.toString();

long begin = System.currentTimeMillis();

//发请求

String result = Request.Post("http://localhost:45678/reflection" + bankAPI.url())

.bodyString(param, ContentType.APPLICATION_JSON)

.execute().returnContent().asString();

log.info("调用银行API {} url:{} 参数:{} 耗时:{}ms", bankAPI.desc(), bankAPI.url(), param, System.currentTimeMillis() - begin);

return result;

}

通过反射来动态获得class的信息,并在runtime的时候完成组装过程。这样做的好处是开发的时候会方便直观很多,然后将逻辑与细节隐藏起来,并且集中放到了一个方法当中,减少了重复,以及维护当中bug的出现。

1.3.4 在代码中的应用

@BankAPI(url = "/bank/createUser", desc = "创建用户接口")

@Data

public class CreateUserAPI extends AbstractAPI {

@BankAPIField(order = 1, type = "S", length = 10)

private String name;

@BankAPIField(order = 2, type = "S", length = 18)

private String identity;

@BankAPIField(order = 4, type = "S", length = 11) //注意这里的order需要按照API表格中的顺序

private String mobile;

@BankAPIField(order = 3, type = "N", length = 5)

private int age;

}

@BankAPI(url = "/bank/pay", desc = "支付接口")

@Data

public class PayAPI extends AbstractAPI {

@BankAPIField(order = 1, type = "N", length = 20)

private long userId;

@BankAPIField(order = 2, type = "M", length = 10)

private BigDecimal amount;

}

最后

关注公众号:程序员白楠楠, 领取2020最新Java面试题手册(200多页PDF文档)。

java 重复代码优化_利用注解 + 反射消除重复代码(Java项目)相关推荐

  1. 利用注解 + 反射消除重复代码(Java项目)

    作者 | Leilei Chen 来源 | https://llchen60.com/ 1. 案例场景 假设银行提供了一些 API 接口,对参数的序列化有点特殊,不使用 JSON,而是需要我们把参数依 ...

  2. 利用注解 + 反射消除重复代码,妙!

    1.1 案例场景 假设银行提供了一些 API 接口,对参数的序列化有点特殊,不使用 JSON,而是需要我们把参数依次拼在一起构成一个大字符串: 1)按照银行提供的API文档顺序,将所有的参数构成定长的 ...

  3. java 清空控制台_利用原生库和JNI(Java原生接口)实现H2数据库漏洞利用

    在H2数据库引擎中获取代码执行权限的技术早已是众所周知,但有个要求就是H2能够动态编译Java代码.而本文将向大家展示以前没有公开过的利用H2的方法,并且无需使用Java编译器,即通过原生库和JNI( ...

  4. java jmx 监控_利用VisualVm和JMX远程监控Java进程

    在前一篇文章里我们发现通过jstatd + VisualVm的方式,不能获得Java进程的CPU.线程.MBean信息,这时JMX就要登场了. 自Java 6开始,Java程序启动时都会在JVM内部启 ...

  5. java怎么防止表单重复提交_如何防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

  6. java cxf 工具_利用CXF工具开发WebService接口

    简单记录一下webservice接口开发以便供以后参考: 一.根据需求编写wsdl文件 WSDL的文件格式和语法我就不多说了,到网上百度一下,或者到W3CSchool去学习都可以,语法很简单,下面是我 ...

  7. java jstack 死锁_利用jstack检测死锁DeadLock

    首先,制造一个死锁程序如下: public class testJstack { final static Object obj_1 = new Object(); final static Obje ...

  8. java 日志切面_自定义注解+面向切面整合的日志记录模块(一)

    java中的常见注解 jdk的自带注解 @Override:告诉编译器我重写了接口方法 @Deprecated:告诉编译器这个方法过时了,不建议使用,Ide会在方法上划横线 @SuppressWarn ...

  9. java游戏循环_利用Java循环语句实现攻击防御塔小游戏

    /* * 时间:2018-3-21 * 目的:practice * 内容:利用Java循环语句实现攻击防御塔小游戏 * 使用Java while语句实现一个英雄攻击防御塔的小程序,防御塔血量总计100 ...

最新文章

  1. 步子太快容易牺牲精度,梯度下降复杂度这一简单道理,获严格数学证明
  2. 不聋不哑,不做当家之解
  3. php curl errno 3,PHP curl_errno函数
  4. PHP中的include和require
  5. macOS下的视频格式转换器
  6. 吉林省计算机科学与技术专业测评,我校计算机科学与技术、英语两个专业在吉林省专业综合评价中获得可喜成绩...
  7. 霍夫变换检测直线的公式推导以及基于opencv的源代码分析并实例实现
  8. 物联网通信-期末复习
  9. 计算机财务模型管理实验内容,财务分析模型实验报告
  10. 电动汽车电池换电站选址与定容(Matlab代码实现)
  11. vnc远程控制软件怎么用,vnc远程控制软件到底该怎么用,使用教程
  12. windows IIS+php配置教程
  13. 黄金分割圆怎么画matlab,黄金分割线的画法和使用方法
  14. python实现微信自动投票_Python——开发一个自动化微信投票器【附代码实例方法】...
  15. 5行代码提升时间序列预测,都有用!
  16. unity初写游戏(四)怪物产生方式
  17. Android怎么自定义充电铃声,充电提示音怎么设置
  18. c语言windows驱动编程入门,Windows驱动开发技术详解 PDF扫描版[175MB]
  19. python 小说下载工具_使用tkinter打造一个小说下载器,想看什么小说,就下什么...
  20. 应用Python3发送邮件示例

热门文章

  1. 治愈系休闲旅游创意海报PSD模板|做设计没思路,快来Get这个小技巧
  2. 设计师网站大全视野导航,灵感、素材统统搞定
  3. UI基本设计素材模板|完整的线框图
  4. 如何做漂亮实用的UI界面?UI/UX设计模板,帮你入手!
  5. php读入输入_php-读取用户输入并检查数据类型
  6. java web试题_Java web开发经典面试题汇总(内附答案详解)
  7. MySQl中文1001无标题_Mysql中字段类型不一致导致索引无效的处理办法
  8. nginx https透明代理_nginx正向https代理配置
  9. C++设计模式详解之适配者模式解析
  10. Python--tkinter迷你天气预报软件(11.8)