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

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

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

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

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

在来看一个例子,如下是一个正常查询返回数据时的接口。
使用SpringAOP的方式修改controller接口返回的数据_第4张图片
那如果查询的时候,本身就没有数据,那么此时返回的数据就是如下这样的。
使用SpringAOP的方式修改controller接口返回的数据_第5张图片
笔者认为,这种一种比较合理的方式。
但是有一种不合理的方式,就是有些接口会返回如下的报文
使用SpringAOP的方式修改controller接口返回的数据_第6张图片
这两种方式区别在于,第一种返回空集合,第二种返回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:hutao
 5. @mail:[email protected]
 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环绕通知改写结果

  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>
			ParameterizedType parameterizedType = (ParameterizedType) type;
			Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
			for (Type t : actualTypeArguments) {
				System.out.println(t.getTypeName());
				//java.util.List
				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:[email protected]
 * @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:[email protected]
	 * @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>
				ParameterizedType parameterizedType = (ParameterizedType) type;
				Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
				for (Type t : actualTypeArguments) {
					System.out.println(t.getTypeName());
					//java.util.List
					if(t.getTypeName().contains("java.util.List")) {
						R<List<?>> reList = new R<>();
						BeanUtils.copyProperties(result, reList);
						reList.setData(new ArrayList<>());
						return reList;
					}
					//其他默认值处理
				}
			}
		}
		return proceed;
	}
}

你可能感兴趣的:(spring,AOP)