1. 案例分析
1.1 案例场景
假设银行提供了一些 API 接口,对参数的序列化有点特殊,不使用 JSON,而是须要咱们把参数依次拼在一块儿构成一个大字符串php
按照银行提供的API文档顺序,将全部的参数构成定长的数据,而且拼接在一块儿做为一整个字符串面试
由于每一种参数都有固定长度,未达到长度须要进行填充处理api
字符串类型参数不满长度部分要如下划线右填充,即字符串内容靠左数组
数字类型的参数不满长度部分以0左填充,即实际数字靠右微信
货币类型的表示须要把金额向下舍入2位到分,以分为单位,做为数字类型一样进行左填充app
参数作MD5 操做做为签名ide
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(); } }
这样作可以基本知足需求,可是存在一些问题:优化
处理逻辑互相之间有重复,稍有不慎就会出现Bugui
处理流程中字符串拼接、加签和发请求的逻辑,在全部方法重复编码
实际方法的入参的参数类型和顺序,不必定和接口要求一致,容易出错
代码层面参数硬编码,没法清晰进行核对
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; }
END 来源:https://llchen60.com/利用注解-反射消除重复代码/ 更多精彩推荐 ☞ Google 鼓励的 13 条代码审查标准,建议收藏! ☞ 这些SQL错误用法,若是常常犯,说明你的水平还很low...☞ 为啥不能用uuid作MySQL的主键?☞ 微信第 1 行代码曝光!☞ 奇葩公司按代码行数算工资,员工一个月提成2.6万遭开除 最后,推荐给你们一个有趣有料的公众号:写代码的渣渣鹏,回复 面试 或 资源 送一你整套开发笔记,按期送书有惊喜哦