如何利用AOP/Aspect来修改方法的入参

问题描述:
最近项目代码过三方测试(国企项目),在一系列代码扫描审计检查下,代码发现一部分修改,例如请求参数发生了编码/加密,导致后台需要对请求的参数进行解码/解密,后端那么接口,总不能挨个,挨个的去修改。

由于之前项目中,已经用了AOP进行代码日志的记录,日志记录如下
在这里插入图片描述
原本代码的核心逻辑如下:因此想着,既然这里已经拿到请求参数了,直接在这里统一解码/加密,就不用对每个接口解码了

        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        methodApiOperation = ms.getMethod().getDeclaredAnnotation(ApiOperation.class);
        if (methodApiOperation != null) {
        	apiOperationDes = methodApiOperation.value();
        }
        logger.info("start-->请求{}模块的[{}]服务",apiDes, apiOperationDes);
        logger.info("  请求地址:{}",url);
        logger.info("  请求方法:{}.{}", abbreviateName(joinPoint.getSignature().getDeclaringTypeName()), methodName);
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
        	if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
        		logger.info("  请求参数{}:{}",i+1,JsonUtils.javaBeanToString(args[i]));
        	}
        }

修改思路1(利用Json格式化)(失败)

这里的请求参数是args 是Object类型,但是原始方法的请求类型肯定是各种自定的VO类
以解码html为例,我这里首先将Object[]挨个元素转成字符串,然后对整个字符串做html解码,将加码后的字符串,在创建一个新的对象赋joinPoint.getArgs的参数。结果发现修改并没有成功。

DecodeURL decodeURL = ms.getMethod().getDeclaredAnnotation(DecodeURL.class);
if(decodeURL != null) {
	Object[] args = joinPoint.getArgs();
	for (int i = 0; i < args.length; i++) {
	if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
		String decode = StringEscapeUtils.unescapeHtml(JsonUtils.javaBeanToString(args[i]));
		logger.info("  解码Html请求参数{}",decode);
		//修改请求入参
		joinPoint.getArgs()[i] = JsonUtils.stringToJavaBean(decode, args[i].getClass());
		}
	}
}

代码执行逻辑是:

  1. 切面中记录请求参数-----》原始文本
  2. 切面中修改请求参数-----》修改后的文本
  3. 实际请求的controller-----》原始文本(也就是修改没有生效)
  4. 切面中调用请求参数-----》修改后的文本
    在这里插入图片描述

修改思路2(原始对象Set值)(有效,但没意义)

思路1中,我们是重新创建一个请求入参,然后把新的请求入参赋值给原始请求入参(Json格式化返回新的对象)
思路2,我们直接在原始的对象进行set值

DecodeURL decodeURL = ms.getMethod().getDeclaredAnnotation(DecodeURL.class);
if(decodeURL != null) {
	for (int i = 0; i < args.length; i++) {
	/*if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
		String decode = StringEscapeUtils.unescapeHtml(JsonUtils.javaBeanToString(args[i]));
		logger.info("  解码Html请求参数{}",decode);
		//修改请求入参
		joinPoint.getArgs()[i] = JsonUtils.stringToJavaBean(decode, args[i].getClass());
	}*/
		if( (args[i] instanceof QueryResult)) {
			QueryResult query = (QueryResult)args[i];
			//修改请求入参
			query.setName(StringEscapeUtils.unescapeHtml(query.getName()));
		}
	}
}

此时查看实际的请求接口,发现值真的被修改了。
在这里插入图片描述
但是这里有个问题,上诉我是通过直接指定类型,然后强转类型,接着调用原对象的set方法,这是是我已知具体类型,具体字段,这样修改,我还不如直接找到原始的接口,在原来的接口里面修改。
AOP

QueryResult query = (QueryResult)args[i];
query.setName(StringEscapeUtils.unescapeHtml(query.getName()));

实际接口

@GetMapping
@DecodeURL
public void exportNxauto(HttpServletResponse response, QueryResult queryResult) {
	queryResult.setName(StringEscapeUtils.unescapeHtml(queryResult.getName()));
}

修改思路3(反射)

总结下思路1,思路1不用类型转换,也不用指定属性,格式化整个Json,然后对整个Json进行中文解码,但是转Json以后,导致重新创建了一个对象。思路2里面虽然没有创建新的对象,但是需要我们强制转化为某个类型,然后调用某个方式,实际请用场景,每个接口的入参的类型都不一样,具体是那个参数需要解码,所以也不知道调用那个Set方法。

Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
	if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
    	logger.info("  请求参数{}:{}",i+1,JsonUtils.javaBeanToString(args[i]));
    }
}
ecodeURL decodeURL = ms.getMethod().getDeclaredAnnotation(DecodeURL.class);
if(decodeURL != null) {
	for (int i = 0; i < args.length; i++) {
		/*if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
			String decode = StringEscapeUtils.unescapeHtml(JsonUtils.javaBeanToString(args[i]));
			logger.info("  解码Html请求参数{}",decode);
			//修改请求入参 失败:这里创建了一个新的对象,原始对象没有修改,修改的是新的对象。
			joinPoint.getArgs()[i] = JsonUtils.stringToJavaBean(decode, args[i].getClass());
		}*/
		/*if( (args[i] instanceof QueryResult)) {
			QueryResult query = (QueryResult)args[i];
			//修改请求入参 修改成功,但是太过于狭义,需要知道类型和具体的属性,然后调用Set方法
			query.setName();
		}*/
		if( !(args[i] instanceof StatHttpServletResponseWrapper)) {
			Class<? extends Object> classz = args[i].getClass();
			//使用反射改成功
			for (Field field : classz.getDeclaredFields()) {
				if(field.getType() == String.class) {
					ReflectionUtils.makeAccessible(field);
					Object value = field.get(args[i]);
					if(value != null) {
						ReflectionUtils.setField(field, args[i], StringEscapeUtils.unescapeHtml(value.toString()));
					}
				}else if (Collection.class.isAssignableFrom(field.getType())) {
				    // 字段是集合类型
				}else if (List.class.isAssignableFrom(field.getType())) {
				    // 字段是List类型
				}else if (field.getType().isArray()) {
				    // 字段是数组类型
				}else if (field.getType().isPrimitive()) {
				    // 字段是基本类型
				}
			}
		}
	}
}

接下来,我们看下运行的日志,可以看到,在我们的实际controller接口中,可以看到字符串类型的已经被html进行解码。
在这里插入图片描述

要实现 AOP 自定义注解获取实体,可以按照以下步骤进行: 1. 定义注解:定义一个注解,用于标记需要被 AOP 拦截的方法。 2. 编写切面:编写一个切面,用于拦截被注解标记的方法,并获取方法参数。 3. 获取实体:在切面中获取方法参数,通过反射的方式获得实体。 下面是一个示例代码,假设我们需要获取被 @LogAnnotation 注解标记的方法的实体: 定义注解: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LogAnnotation { String value() default ""; } ``` 编写切面: ```java @Component @Aspect public class LogAspect { @Around("@annotation(com.example.demo.annotation.LogAnnotation)") public Object around(ProceedingJoinPoint pjp) throws Throwable { // 获取方法参数 Object[] args = pjp.getArgs(); // 判断参数是否为实体类 for (Object arg : args) { if (arg instanceof BaseEntity) { // 获取实体 BaseEntity entity = (BaseEntity) arg; // TODO: 处理实体 } } // 执行方法 Object result = pjp.proceed(); return result; } } ``` 在上面的代码中,我们使用 @Around 注解标记了 around 方法,并指定了切点表达式 @annotation(com.example.demo.annotation.LogAnnotation),表示拦截被 @LogAnnotation 注解标记的方法。在 around 方法中,通过 ProceedingJoinPoint 对象获取方法参数,然后判断参数是否为实体类,如果是实体类,则获取实体,进行处理。 注意,在获取实体时,我们使用了 instanceof 判断参数是否为实体类,因此需要保证实体类继承了一个 BaseEntity 类或接口,否则无法判断参数是否为实体类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值