1为什么需要修改返回接口的数据?
先看一个关于返回接口数据中包含时间的接口,如下接口中的birth属性,是日期,假设我们不做任何处理,那么在页面,我们看到的将是如下的时间显示效果,这明显不是我们想要的。
最Low B的方式,直接将birth属性变成字符串格式,然后将时间格式化成字符串
当然上面这种代码,要是写出来的话,可以考虑换行业了。我们都知道,在MVC中,有个Json转换器,这个Json转换器可以将我们controller返回的对象格式化成Json。
因此我们可以在格式化的时候,指定时间格式。这种方式明显比第一种方式要友好多。
但是这种方式也存在一个问题,那就是假设如果我们某个时间的属性,开发人员在开发时,忘记了加上对应Json包的时间格式化注解,比如这里采用的com.fasterxml.jackson.annotation.JsonFormat这个注解。那么就可能就造成没有添加这个注解的时间字段,在返回时间时,时间格式不是我们想要的。
所以,针对上述这个问题,我们还有解决方案,那就是采用如下配置,指定jackson在进行时格式化时,遇到时间类型的,将其格式为指定的格式。这种方式的好处就是,所有的日期类型的属性在返回时,MVC的Json解析器,都会将其格式化。因此不会存在遗漏注解的情况。
2利用AOP的思想改写controller的返回值
在来看一个例子,如下是一个正常查询返回数据时的接口。
那如果查询的时候,本身就没有数据,那么此时返回的数据就是如下这样的。
笔者认为,这种一种比较合理的方式。
但是有一种不合理的方式,就是有些接口会返回如下的报文
这两种方式区别在于,第一种返回空集合,第二种返回null,相信有点开发规模的团队,都有明确的开发规范,是禁止如下代码形式的,因为写方法返回了null,及其造成调用者使用的时候出现空指针异常。
public String getXxxxx() {
//逻辑处理
return null;
}
public List<String> getListXxxx(){
//逻辑处理
return null;
}
规范的代码应该如下所示
public String getXxxxx() {
String res = "";
//逻辑处理
return res;
}
public List<String> getListXxxx(){
List<String> list = new ArrayList<>();
//逻辑处理
return list;
}
当调用者,调用第一种方法的时候,不得不添加一堆判断空指针的代码
例如如下所示,这样增加了代码的冗余量。
String xxxxx = getXxxxx();
if(xxxxx != null) {
xxxxx.equals("abcdefg");
}
List<String> listXxxx = getListXxxx();
if(listXxxx != null) {
listXxxx.forEach(temp->{
System.out.println(temp);
});
}
而第二种方式,我们代码则可以省略一系列的null判断,因为此时,这些方法不会返回null
String xxxxx = getXxxxx();
xxxxx.equals("abcdefg");
List<String> listXxxx = getListXxxx();
listXxxx.forEach(temp->{
System.out.println(temp);
});
最常见的例子,就是目前我们使用的一系列ORM框架,例如Mybatis(Mybatis-plus),在进行相关list查询的时候,就不会返回null,在查询不到数据的时候,就会返回空集合,而不是null。
但是每个项目组的开发人员,技能素质不一,难免有人返回值就是返回null,为了避免这个问题就是,能不能像我们修改返回数据中的日期类型一样嗯?以防止部分不遵守开发规范的人,返回不符合我们预期管理的数据嗯?
本例子中,我们就两种方式来处理
- 方式1:Json格式化时,将null格式为空字符串
- 方式2:结合AOP,获取controller方法返回值,构造初始值
方式1:
这种方式简单粗暴,就是Json格式化以后,遇到null,就把null设置为空字符串。缺点就是,因为是null,所以无法得知null之前的数据类型是什么,
例如,如下数据类型,在转Json之后,都是null,无法得知null之前是什么类型
String aaa = null;
Integer bbb = null;
List<Integer> ccc = null;
Map<String,List<Integer>> ddd = null;
Date fff = null;
具体代码如下
/**
3. @description:将Json中的null节点,变成空字符串
4. @author:hutao
5. @mail:hutao1@epri.sgcc.com.cn
6. @date:2023年4月12日 上午10:50:56
*/
@SpringBootConfiguration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("");
}
});
return objectMapper;
}
}
方式2:
这种方式利用AOP返回值,可以根据方法的返回值的,去推算出null之前的数据类型是什么。
2.1拦截controller方法
例如,我们要拦截如下的方法
@ApiOperation(value = "按照条件进行查询仿真任务表")
@PostMapping("/info/list")
public R<List<SgSimTaskB>> listTaskInfoByCondition(@RequestBody ConditionQuery conditionQuery) {
}
2.2设置拦截切入点
本案例中,使用了OpenAPI(swagger3),因此我们可以利用Swagger的注解来进行切入
//切入OpenAPI接口
@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
public void pointcut() {
}
2.3环绕通知改写结果
- ProceedingJoinPoint
@Around("pointcut()")
public Object modifyResult(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed = joinPoint.proceed();
if(proceed instanceof R) {
R<?> result = (R<?>) proceed;
//如果data数据为null,则去获取返回data数据方法的返回值类型
if(result.getData() == null) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Type type = signature.getMethod().getGenericReturnType();
System.out.println(type);
//com.plan.map.config.query.R<java.util.List<com.plan.map.module.test.entity.SgTmsNeB>>
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type t : actualTypeArguments) {
System.out.println(t.getTypeName());
//java.util.List<com.plan.map.module.test.entity.SgTmsNeB>
if(t.getTypeName().contains("java.util.List")) {
R<List<?>> reList = new R<>();
BeanUtils.copyProperties(result, reList);
reList.setData(new ArrayList<>());
return reList;
}
//其他默认值处理
}
}
}
return proceed;
}
完整代码配置
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.SpringBootConfiguration;
import com.plan.map.config.query.R;
/**
* @description:使用AOP修改返回值结果
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年4月11日 上午10:55:14
*/
@Aspect
@SpringBootConfiguration
public class ModifyResultConfig {
@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
public void pointcut() {
}
/**
* @description:修改返回参数中的Data为null的时候,为其初始化默认值
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年4月11日 上午10:55:32
*/
//注意:使用@AfterReturning只能修改基本数据类型,不能修改引用类型,即使new一个新对象返回,一样不生效
@Around("pointcut()")
public Object modifyResult(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed = joinPoint.proceed();
if(proceed instanceof R) {
R<?> result = (R<?>) proceed;
//如果data数据为null,则去获取返回data数据方法的返回值类型
if(result.getData() == null) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Type type = signature.getMethod().getGenericReturnType();
System.out.println(type);
//com.plan.map.config.query.R<java.util.List<com.plan.map.module.test.entity.SgTmsNeB>>
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type t : actualTypeArguments) {
System.out.println(t.getTypeName());
//java.util.List<com.plan.map.module.test.entity.SgTmsNeB>
if(t.getTypeName().contains("java.util.List")) {
R<List<?>> reList = new R<>();
BeanUtils.copyProperties(result, reList);
reList.setData(new ArrayList<>());
return reList;
}
//其他默认值处理
}
}
}
return proceed;
}
}