本文以自动处理金额字段为参考,将返回结果自动匹配相对应的单位(金额单位:【元,万元,亿】),且支持单位是否放置额外字段等。
本文涉及技术点:自定义注解、AOP、反射、递归
一、前提背景
α、回参原始样例
{
"code": 0,
"msg": "成功",
"data": {
"money": "20000000",
"moneyUnit": null
}
}
β、需要处理的两个场景
1、转换money,且将单位跟在money后。
{
"code": 0,
"msg": "成功",
"data": {
"money": "2000.00万元",
"moneyUnit": null
}
}
2、转换money,将单位放入额外字段moneyUnit。
{
"code": 0,
"msg": "成功",
"data": {
"money": "2000.00",
"moneyUnit": "万元"
}
}
二、设计思路
α、利用AOP在spring返回responseBody时切入处理
b、根据反射拿到DTO内部的属性和值
γ、若DTO内含DTO,用递归处理
δ、最终解析为基础类型进入处理方法,处理方法中根据自定义注解实现多种场景配置
三、代码演示
α、主逻辑代码
1、实现ResponseBodyAdvice类,重写supports,beforeBodyWrite方法。
// ControllerAdvice用于对Controller进行“切面”环绕的注解
@ControllerAdvice
@Slf4j
// 实现ResponseBodyAdvice类
public class ResponseDealAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
// 重写此方法
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter,
MediaType mediaType, Class aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
try {
// 对统一返回类Result开始反射解析(统一返回类见四-α))
if (o instanceof Result) {
Result resultContentVO = (Result) o;
// 获取统一返回类中的实际返回类的所有属性
Field[] declaredFields = resultContentVO.getData().getClass()
.getDeclaredFields();
// 调用递归方法(方法见三-α-2)
recursion(result, declaredFields);
}
} catch (Exception e) {
log.error("ResponseDealAdvice beforeBodyWrite error:" + e);
}
return o;
}
//省略下方代码
..........
}
2、递归处理DTO方法,递归解析,一直解析到基础类型。
目前DTO仅支持:List、DTO(内部类)、基础数据类型
/**
* 递归处理DTO属性
* 支持DTO类型:list,内部DTO,基本数据类型
* @param o
* @param declaredFields
*/
void recursion(Object o, Field[] declaredFields) {
try {
for (Field declaredField : declaredFields) {
Field declaredField = declaredFields[i];
//如果属性是List就解析list的对象
if (declaredField.getType().getName().contains("List")) {
// 反射获取DTO里xxxList的get方法
Method get = o.getClass().getMethod("get"
+ declaredField.getName().substring(0, 1).toUpperCase()
+ declaredField.getName().substring(1));
// 反射获取xxxList实例
List invoke = (List) get.invoke(o);
if (!CollectionUtils.isEmpty(invoke)) {
// 遍历xxxList
invoke.forEach(t -> {
try {
// 获取所有属性
Field[] declaredFields1 = t.getClass()
.getDeclaredFields();
// 对属性遍历
for (Field field : declaredFields1) {
// 如果List中的属性有list和DTO递归处理
if (field.getType().getName().contains("List")
|| field.getType().getName().contains("$")) {
recursion(t, declaredFields1);
}
// 剩下(基本类型)进入处理方法(方法见三-α-3)
dealMoney(t, field);
}
} catch (Exception e) {
log.error("ResponseDealAdvice listForEach error:" + e);
}
});
}
} else if (declaredField.getType().getName().contains("$")) {
// 如果属性是内部DTO
// 获取内部DTO所有的属性
Field[] nestedClassFields = declaredField.getType().getDeclaredFields();
// 反射拿到内部类的名字 !!!ps:内部DTO名字保持和类型一致!!!
String nestedClassName = declaredField.getType().getName()
.substring(declaredField.getType().getName().indexOf("$")));
Method method = o.getClass().getMethod("get" + nestedClassName);
// 反射获取到内部DTO实例
Object invoke = method.invoke(o);
// 进入到递归方法
recursion(invoke, nestedClassFields);
} else {
// 非List,非DTO,基本类型直接处理(方法见三-α-3)
dealMoney(o, declaredField);
}
}
} catch (Exception e) {
log.error("ResponseDealAdvice recursion error:" + e);
}
}
3、基础类型统一处理方法
/**
* 统一处理金额
*
* @param t
* @param filed
*/
void dealMoney(Object t, Field filed) {
try {
// 有DealMoney注解的属性进入(注解类见四-β)
if (filed.isAnnotationPresent(DealMoney.class)) {
// 获取字段名
String filedName = filed.getName();
// 反射获取字段的get方法
Method getMethodMain = t.getClass().getMethod("get"
+ filedName.substring(0, 1).toUpperCase()
+ filedName.substring(1));
// 根据发射的get方法获取实例的money值
String money = (String) getMethodMain.invoke(t);
// money为空处理
if (money == null || money.equals("-")) {
return;
}
// 统一处理money,计算money是元/万元/亿元哪种,放入新DTO类返回(方法见三-α-4)
MoneyVO moneyAndUnit = getMoneyAndUnit(new BigDecimal(money));
// 通过反射获取注解字段的set方法
Method setMethodMain = t.getClass().getMethod("set"
+ filedName.substring(0, 1).toUpperCase()
+ filedName.substring(1), String.class);
// 获取字段的注解值
DealMoney annotation = filed.getAnnotation(DealMoney.class);
if (annotation.HavaUnit() == 1) {
// 默认1 注解字段+额外字段填充
// 填充注解字段
setMethodMain.invoke(t, moneyAndUnit.getMoneyAmount());
// 获取额外字段单位的set方法
Method setMethodUnit = t.getClass().getMethod("set"
+ filedName.substring(0, 1).toUpperCase()
+ filedName.substring(1) + "Unit", String.class);
// 填充单位字段
setMethodUnit.invoke(t, moneyAndUnit.getMoneyUnit());
} else if (annotation.HavaUnit() == 2) {
// 2 金额+单位填充到注解字段中
setMethodMain.invoke(t, moneyAndUnit.getMoneyAmount()
+ moneyAndUnit.getMoneyUnit());
} else {
// 没有输出错误
log.error("ResponseDealAdvice dealMoney error: missing havaUnit type"
+ annotation.HavaUnit());
}
}
} catch (Exception e) {
log.error("ResponseDealAdvice dealMoney error:" + e);
}
}
4、统一处理money类
public static MoneyVO getMoneyAndUnit(BigDecimal newMoney){
// VO类见三-α-5
MoneyVO moneyVO = new MoneyVO();
moneyVO.setMoneyUnit("元");
moneyVO.setMoneyAmount("-");
// 入参为空,直接返回
if(newMoney==null) return moneyVO;
if(newMoney.compareTo(new BigDecimal(10000))>=0){
// 值大于1w,设置单位,处理money值回填vo
moneyVO.setMoneyAmount(
newMoney.divide(BigDecimal.valueOf(10000),
2, BigDecimal.ROUND_HALF_UP).toString());
moneyVO.setMoneyUnit("万元");
} else if (newMoney.compareTo(new BigDecimal(1000000000)) >= 0) {
// 值大于1w,设置单位,处理money值回填vo
moneyVO.setMoneyAmount(
newMoney.divide(BigDecimal.valueOf(1000000000),
2, BigDecimal.ROUND_HALF_UP).toString());
moneyVO.setMoneyUnit("亿元");
} else moneyVO.setMoneyAmount(newMoney.setScale(2, BigDecimal.ROUND_HALF_UP).toString());
return moneyVO;
}
5、统一处理money方法所需VO类
@Getter
@Setter
public class MoneyVO implements Serializable {
/**
* 金额量
*/
private String moneyAmount;
/**
* 金额单位
*/
private String moneyUnit;
}
四、辅助代码
α、统一返回类
@Data
public class Result<T> {
private Integer code;
private String msg;
private T data;
private Result(Integer code, String msg){
this.code = code;
this.msg = msg;
}
/**
* 成功返回,包含对象
* @param t
* @param <T>
* @return
*/
public static <T> Result<T> success(T t){
Result<T> result = new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMsg());
result.setData(t);
return result;
}
/**
* 成功返回,无对象
* @return
*/
public static <T> Result<T> success(){
return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMsg());
}
/**
* 失败返回
* @param code
* @param msg
* @return
*/
public static <T> Result<T> error(Integer code, String msg){
return new Result<>(code, msg);
}
}
β、统一注解类
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DealMoney {
/**
* 解决单位问题
* 默认为
* 1:金额和单位字段都填充
* 2:金额和单位合并填充金额字段
*/
int HavaUnit() default 1;
}
五、测试代码
α、测试DTO
@Data
public class TestDTO {
@DealMoney(HavaUnit = 1)
private String money;
private String moneyUnit;
private List<InnerClassDTO> aList;
@NoArgsConstructor
@Data
public static class InnerClassDTO {
@DealMoney(HavaUnit = 2)
private String innerMoney;
private String innerMoneyUnit;
private Inner2ClassDTO inner2Class;
}
@NoArgsConstructor
@Data
public static class Inner2ClassDTO {
@DealMoney(HavaUnit = 1)
private String innerMoney;
private String innerMoneyUnit;
}
}
β、测试controller
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/unit")
public Result<TestDTO> test(){
TestDTO testDTO = new TestDTO();
testDTO.setMoney("20000000");
List<TestDTO.InnerClassDTO> aList = new ArrayList<>();
TestDTO.InnerClassDTO innerDTO = new TestDTO.InnerClassDTO();
innerDTO.setInnerMoney("10000000");
TestDTO.Inner2ClassDTO innner2DTO = new TestDTO.Inner2ClassDTO();
innner2DTO.setInnerMoney("5000");
innerDTO.setInner2Class(innner2DTO);
aList.add(innerDTO);
testDTO.setAList(aList);
return Result.success(testDTO);
}
}
γ、测试结果
{
"code": 0,
"msg": "成功",
"data": {
"money": "2000.00",
"moneyUnit": "万元",
"alist": [
{
"innerMoney": "1000.00万元",
"innerMoneyUnit": null,
"inner2Class": {
"innerMoney": "5000.00",
"innerMoneyUnit": "元"
}
}
]
}
}