点击上方“Java后端编程”,关注公众号
面试刷图,查缺补漏

1. 案例分析

1.1 案例场景

假设银行提供了一些 API 接口,对参数的序列化有点特殊,不使用 JSON,而是需要我们把参数依次拼在一起构成一个大字符串
按照银行提供的API文档顺序,将所有的参数构成定长的数据,并且拼接在一起作为一整个字符串
因为每一种参数都有固定长度,未达到长度需要进行填充处理
  • 字符串类型参数不满长度部分要以下划线右填充,即字符串内容靠左
  • 数字类型的参数不满长度部分以0左填充,即实际数字靠右
  • 货币类型的表示需要把金额向下舍入2位到分,以分为单位,作为数字类型同样进行左填充
  • 参数做MD5 操作作为签名

1.2 初步代码实现

publicclassBankService
{


//创建用户方法
publicstatic 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();

    }


//支付方法
publicstatic 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
publicclassCreateUserAPI
{

private
 String name;

private
 String identity;

private
 String mobile;

privateint
 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 {

intorder()default -1
;

intlength()default -1
;

String type()default ""
;

}

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

privatestatic 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))

thrownew
 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
publicclassCreateUserAPIextendsAbstractAPI
{

@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
)

privateint
 age;

}




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

@Data
publicclassPayAPIextendsAbstractAPI
{

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

privatelong
 userId;

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

private
 BigDecimal amount;

}

END
来源:https://llchen60.com/利用注解-反射消除重复代码/
 关注公众号:Java后端编程,回复下面关键字 
要Java学习完整路线,回复  路线 
缺Java入门视频,回复 视频 
要Java面试经验,回复  面试 
缺Java项目,回复: 项目 
进Java粉丝群: 加群 
PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。
(完)
加我"微信获取一份 最新Java面试题资料
请备注:666不然不通过~
最近好文
最近面试BAT,整理一份面试资料Java面试BAT通关手册,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:关注公众号并回复 java 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡
继续阅读
阅读原文