> 文章列表 > 使用SpringAOP的方式修改controller接口返回的数据

使用SpringAOP的方式修改controller接口返回的数据

使用SpringAOP的方式修改controller接口返回的数据

1为什么需要修改返回接口的数据

先看一个关于返回接口数据中包含时间的接口,如下接口中的birth属性,是日期,假设我们不做任何处理,那么在页面,我们看到的将是如下的时间显示效果,这明显不是我们想要的。
使用SpringAOP的方式修改controller接口返回的数据
最Low B的方式,直接将birth属性变成字符串格式,然后将时间格式化成字符串
使用SpringAOP的方式修改controller接口返回的数据
当然上面这种代码,要是写出来的话,可以考虑换行业了。我们都知道,在MVC中,有个Json转换器,这个Json转换器可以将我们controller返回的对象格式化成Json。
因此我们可以在格式化的时候,指定时间格式。这种方式明显比第一种方式要友好多。
使用SpringAOP的方式修改controller接口返回的数据
但是这种方式也存在一个问题,那就是假设如果我们某个时间的属性,开发人员在开发时,忘记了加上对应Json包的时间格式化注解,比如这里采用的com.fasterxml.jackson.annotation.JsonFormat这个注解。那么就可能就造成没有添加这个注解的时间字段,在返回时间时,时间格式不是我们想要的。

所以,针对上述这个问题,我们还有解决方案,那就是采用如下配置,指定jackson在进行时格式化时,遇到时间类型的,将其格式为指定的格式。这种方式的好处就是,所有的日期类型的属性在返回时,MVC的Json解析器,都会将其格式化。因此不会存在遗漏注解的情况。
使用SpringAOP的方式修改controller接口返回的数据

2利用AOP的思想改写controller的返回值

在来看一个例子,如下是一个正常查询返回数据时的接口。
使用SpringAOP的方式修改controller接口返回的数据
那如果查询的时候,本身就没有数据,那么此时返回的数据就是如下这样的。
使用SpringAOP的方式修改controller接口返回的数据
笔者认为,这种一种比较合理的方式。
但是有一种不合理的方式,就是有些接口会返回如下的报文
使用SpringAOP的方式修改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. 方式1:Json格式化时,将null格式为空字符串
  2. 方式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:hutao5. @mail:hutao1@epri.sgcc.com.cn6. @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>() {@Overridepublic 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环绕通知改写结果

  1. 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;}
}