【微服务】springboot 自定义注解+反射+aop实现动态修改请求参数

目录

一、前言

二、动态修改接口请求参数的场景

2.1 动态修改请求参场景汇总

2.1.1 数据格式标准化

2.1.2 安全需要

2.1.3 参数校验与默认值设定

2.1.4 数据隐私保护

2.1.5 适配不同客户端

2.1.6 统计与监控

2.1.7 高级功能特性

三、springboot 使用过滤器和拦截器动态修改接口请求参数

3.1 使用过滤器动态修改请求参数

3.1.1 自定义 HttpServletRequest 包装类 RequestWrapper

3.1.2 自定义 HttpServletResponse 包装类 ResponseWrapper

3.1.3 自定义过滤器 ParamModifyFilter

3.1.4 测试接口

3.2 使用拦截器动态修改请求参数

3.2.1 自定义 HttpServletRequest 包装类 RequestWrapper

3.2.2 自定义CustomInterceptor

3.2.3 自定义过滤器

3.2.4 请求参数对象

3.2.5 测试接口

3.2.6 效果测试

四、springboot使用反射+aop实现动态修改请求参数

4.1 实现思路

4.2 代码实现过程

4.2.1 导入aop依赖

4.2.2 自定义注解

4.2.3 请求对象参数

4.2.4 自定义aop实现类

4.2.5 测试接口

4.2.6 测试效果展示

4.2.7 扩展补充点

五、写在文末


一、前言

在日常使用springboot的微服务项目开发中,可能会遇到这样的业务场景,针对某些到达服务端的接口请求参数,需要做预处理,比如对请求参数中的合法性、安全性进行过滤,再比如说,某些接口的业务,需要在请求到达接口之前进行特殊的赋值操作等,类似的业务场景还有很多,本文将分享如何基于自定义注解和aop的方式实现尽可能通用的解决方案。

二、动态修改接口请求参数的场景

2.1 动态修改请求参场景汇总

动态修改接口的请求参数在多种场景下可能是必要的,下面汇聚了一些常见的场景:

2.1.1 数据格式标准化

在不同客户端之间可能存在不同的数据格式,服务器需要统一处理这些格式差异。例如:

  • 日期时间格式统一:客户端提交的日期时间格式不一致,服务器需要统一为某种格式。

  • 枚举类型映射:客户端可能使用不同的枚举值表示相同的状态,服务器需要将这些不同的值映射为内部使用的统一枚举值。

2.1.2 安全需要

为了增强安全性,可能需要对请求参数进行加密、哈希或其他形式的安全处理:

  • 敏感信息加密:对于用户的密码、银行卡号等敏感信息,在传输前进行加密处理。

  • 防止注入攻击:对字符串类型的参数进行转义处理,防止 SQL 注入、XSS 攻击等安全问题。

2.1.3 参数校验与默认值设定

在请求参数到达业务逻辑层之前,对参数进行预处理,确保参数的有效性和一致性:

  • 参数校验:检查参数是否符合预期格式,如手机号、邮箱地址等。

  • 默认值设定:某些参数如果没有提供,则为其设置默认值。

2.1.4 数据隐私保护

在涉及用户隐私数据的情况下,可能需要对某些敏感字段进行脱敏处理:

  • 脱敏处理:例如电话号码、身份证号等信息部分替换为星号或其他字符。

2.1.5 适配不同客户端

不同客户端(如移动应用、Web 应用等)可能有不同的数据需求或格式偏好:

  • 适配不同客户端:根据客户端类型动态调整返回的数据格式或内容。

  • 多语言支持:根据客户端的语言偏好动态调整请求参数中的语言标识。

2.1.6 统计与监控

为了统计或监控的目的,可能需要在请求中附加额外的信息:

  • 添加跟踪信息:例如在请求中加入唯一标识符,方便后续的日志分析。

  • 记录来源信息:记录请求来源的 IP 地址、客户端类型等信息。

2.1.7 高级功能特性

某些高级功能可能需要特殊的参数处理机制:

  • 批处理请求:将多个请求合并为一个请求,减少网络开销。

  • 异步请求处理:对异步请求进行特殊处理,如设置回调地址。

三、springboot 使用过滤器和拦截器动态修改接口请求参数

如果将这个问题当作一个需求来看,在正式开始实现之前,建议全面的深入的思考一下你的解决方案是否合理,比如说:

  • 是为了解决某个特定的接口修改请求参数?

  • 针对某一类业务涉及到的所有接口均需要实现请求参数的修改?

  • 还是某一类参数涉及的接口需要修改呢?

其实不同的场景分类,实际在解决问题时使用的技术,以及技术实现的复杂程度、通用性等方面也是大不一样的,下面列举了在springboot开发中针对动态修改请求参数这个需求的常用实现思路。

3.1 使用过滤器动态修改请求参数

过滤器或拦截器在微服务的开发中可以说应用的场景非常多,利用过滤器或拦截器可以在请求到达接口之前做一些请求预处理相关的操作,比如拦截非法请求,参数XSS校验,对请求IP进行审计、拦截、限流等,也可以进行全局的会话凭证的校验等,针对请求参数的预处理或修改请求参数,也可以作为一个考虑和选择的方案,下面看具体的代码实现过程。

3.1.1 自定义 HttpServletRequest 包装类 RequestWrapper

在 Servlet 中,原始的 HttpServletRequest 对象中的请求流(即请求体)只能读取一次。这是因为 HTTP 协议是基于流的协议,服务器在读取请求流时会将其消耗掉,一旦读取完毕,就无法再次读取。当 Servlet 容器读取完请求流后,会将请求的内容解析并储存在相应的属性中,如请求参数、请求头信息等。在后续的处理过程中,Servlet 可以从这些属性中获取请求内容,而不必再次读取请求流。因此,我们需要自定义 RequestWrapper 将请求流保存下来,并提供方法来多次读取请求体的内容。

package com.congge.filter.v2;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;/*** HttpServletRequest 包装类,允许在 Servlet 中多次读取请求体内容* 重写了 getInputStream()方法和 getReader() 方法,返回可以多次读取的流。*/
public class OwnRequestWrapper extends HttpServletRequestWrapper {private final byte[] body;/*** 构造 RequestWrapper 对象** @param request 原始 HttpServletRequest 对象* @param context 请求体内容*/public OwnRequestWrapper(HttpServletRequest request, String context) {super(request);this.body = context.getBytes(StandardCharsets.UTF_8);}/*** 重写 getInputStream 方法,返回经过包装后的 ServletInputStream 对象** @return 经过包装后的 ServletInputStream 对象*/@Overridepublic ServletInputStream getInputStream() {return new ServletInputStreamWrapper(new ByteArrayInputStream(body));}/*** 重写 getReader 方法,返回经过包装后的 BufferedReader 对象** @return 经过包装后的 BufferedReader 对象*/@Overridepublic BufferedReader getReader() {return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));}/*** 私有内部类,用于包装 ServletInputStream 对象*/private static class ServletInputStreamWrapper extends ServletInputStream {private final ByteArrayInputStream inputStream;/*** 构造函数,传入待包装的 ByteArrayInputStream 对象** @param inputStream 待包装的 ByteArrayInputStream 对象*/public ServletInputStreamWrapper(ByteArrayInputStream inputStream) {this.inputStream = inputStream;}/*** 重写 read 方法,读取流中的下一个字节** @return 读取到的下一个字节,如果已达到流的末尾,则返回-1*/@Overridepublic int read() {return inputStream.read();}/*** 覆盖 isFinished 方法,指示流是否已完成读取数据** @return 始终返回 false,表示流未完成读取数据*/@Overridepublic boolean isFinished() {return false;}/*** 重写 isReady 方法,指示流是否准备好进行读取操作** @return 始终返回 false,表示流未准备好进行读取操作*/@Overridepublic boolean isReady() {return false;}/*** 重写 setReadListener 方法,设置读取监听器** @param readListener 读取监听器*/@Overridepublic void setReadListener(ReadListener readListener) {}}
}

3.1.2 自定义 HttpServletResponse 包装类 ResponseWrapper

与请求流(即请求体)一样,原始的 HttpServletResponse 对象中的响应流(即响应体)只能写入一次。当服务器在向客户端发送响应时,会将响应流写入到网络传输通道中,一旦写入完毕,就无法再次修改或写入。因此需要通过自定义 ResponseWrapper 包装原始的 HttpServletResponse 对象并重写其输出流或者输出写方法,从而实现对响应流的修改和控制。

package com.congge.filter.v2;import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;public class ResponseWrapper extends HttpServletResponseWrapper {private final ByteArrayOutputStream outputStream;private ServletOutputStream servletOutputStream;private PrintWriter writer;/*** 构造函数,传入原始的 HttpServletResponse 对象** @param response 原始的 HttpServletResponse 对象*/public ResponseWrapper(HttpServletResponse response) {super(response);this.outputStream = new ByteArrayOutputStream();}/*** 重写 getOutputStream 方法,返回经过包装后的 ServletOutputStream 对象** @return 经过包装后的 ServletOutputStream 对象*/@Overridepublic ServletOutputStream getOutputStream() {if (servletOutputStream == null) {servletOutputStream = new ServletOutputStreamWrapper(outputStream);}return servletOutputStream;}/*** 重写 getWriter 方法,返回经过包装后的 PrintWriter 对象** @return 经过包装后的 PrintWriter 对象*/@Overridepublic PrintWriter getWriter() {if (writer == null) {writer = new PrintWriter(getOutputStream());}return writer;}/*** 获取响应数据,并指定字符集** @param charsetName 字符集名称* @return 响应数据字符串*/public String getResponseData(String charsetName) {Charset charset = Charset.forName(charsetName);byte[] bytes = outputStream.toByteArray();return new String(bytes, charset);}/*** 设置响应数据,并指定字符集** @param responseData 响应数据字符串* @param charsetName  字符集名称*/public void setResponseData(String responseData, String charsetName) {Charset charset = Charset.forName(charsetName);byte[] bytes = responseData.getBytes(charset);outputStream.reset();try {outputStream.write(bytes);} catch (IOException e) {// 处理异常}setCharacterEncoding(charsetName);}/*** 私有内部类,用于包装 ServletOutputStream 对象*/private static class ServletOutputStreamWrapper extends ServletOutputStream {private final ByteArrayOutputStream outputStream;/*** 构造函数,传入待包装的 ByteArrayOutputStream 对象** @param outputStream 待包装的 ByteArrayOutputStream 对象*/public ServletOutputStreamWrapper(ByteArrayOutputStream outputStream) {this.outputStream = outputStream;}/*** 重写 write 方法,将指定字节写入输出流** @param b 字节*/@Overridepublic void write(int b) {outputStream.write(b);}/*** 重写 isReady 方法,指示输出流是否准备好接收写入操作** @return 始终返回 false,表示输出流未准备好接收写入操作*/@Overridepublic boolean isReady() {return false;}/*** 重写 setWriteListener 方法,设置写入监听器** @param writeListener 写入监听器*/@Overridepublic void setWriteListener(WriteListener writeListener) {}}
}

3.1.3 自定义过滤器 ParamModifyFilter

这里的需求是:

  • 请求到达接口之前,对请求参数进行修改;

  • 在响应返回之前,对响应结果进行处理;

请求参数进行修改,利用过滤器的实现思路如下:

  • 获取请求体内容;

  • 修改请求体内容;

  • 将修改后的请求对象替换原来请求对象,以便后续接口获取修改后的参数;

对响应结果的参数进行修改,利用过滤器的实现思路如下:

  • 获取响应数据;

  • 对响应数据进行处理;

  • 将修改后的数据作为最终结果返回;

最后,为了确保每个请求在请求时只会被过滤一次,这里可以通过继承 OncePerRequestFilter 来定义自己的过滤器,代码如下:

package com.congge.filter.v2;import com.alibaba.fastjson.JSONObject;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;@WebFilter(urlPatterns = "/aop/*",filterName = "myFilter")
public class ParamModifyFilter extends OncePerRequestFilter {static Map<String, Map> urlParamMap = new HashMap();static {Map paramMap = new HashMap();paramMap.put("name","mike");paramMap.put("address","guangzhou");urlParamMap.put("/aop/post/test",paramMap);}@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 1. 从 HttpServletRequest 对象中获取请求体内容String requestBody = getRequestBody(httpServletRequest);// 2. 解析请求体内容为JSON对象JSONObject jsonBody = JSONObject.parseObject(requestBody);// 3. 修改请求体内容String requestURI = httpServletRequest.getRequestURI();if(urlParamMap.containsKey(requestURI)){Map paramMap = urlParamMap.get(requestURI);paramMap.forEach((key,val)->{if(jsonBody.containsKey(key)){jsonBody.put(String.valueOf(key),paramMap.get(key));}});}// 4. 包装 HttpServletRequest 对象为自定义的 RequestWrapper 对象,以便后续的处理OwnRequestWrapper requestWrapper = new OwnRequestWrapper(httpServletRequest, jsonBody.toJSONString());// 5. 包装 HttpServletResponse 对象为自定义的 ResponseWrapper 对象,以便后续的处理ResponseWrapper responseWrapper = new ResponseWrapper(httpServletResponse);// 6. 调用下一个过滤器或 ServletfilterChain.doFilter(requestWrapper, responseWrapper);// 7. 获取响应数据String responseData = responseWrapper.getResponseData(StandardCharsets.UTF_8.name());// 8. 解析响应数据为JSON对象JSONObject jsonData = JSONObject.parseObject(responseData);// 9. 在这里可以对响应数据进行处理jsonData.put("responseNewKey", "responseNewValue");// 10. 将修改后的 JSON 对象转换为字符串responseData = jsonData.toJSONString();// 11. 将修改后的 JSON 对象设置为最终的响应数据responseWrapper.setResponseData(responseData, StandardCharsets.UTF_8.name());// 12. 将响应数据写入原始的响应对象,解决响应数据无法被多个过滤器处理问题OutputStream outputStream = httpServletResponse.getOutputStream();outputStream.write(responseData.getBytes(StandardCharsets.UTF_8));outputStream.flush();}/*** 获取请求体内容。** @param request HttpServletRequest对象* @return 请求体内容* @throws IOException 如果读取请求体内容时发生I/O异常*/private String getRequestBody(HttpServletRequest request) throws IOException {BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));StringBuilder sb = new StringBuilder();String line;while ((line = reader.readLine()) != null) {sb.append(line);}return sb.toString();}
}

补充说明:

在这段代码中,我们通过自定义过滤器的方式,拦截指定类型的接口,并获取接口中的参数,并对特定的接口中的请求参数进行修改,同时,也对接口执行完成之后的返回值进行修改

3.1.4 测试接口

添加一个测试接口

    @PostMapping("/aop/post/test")public Object testPost(@RequestBody UserRequest userRequest) {System.out.println("进入接口");String myParam1 = userRequest.getName();String myParam2 = userRequest.getAddress();System.out.println(myParam1 + ":" + myParam2);return new UserRequest(myParam1,myParam2);}

如果没有过滤器的情况下,接口的响应如下:

如果上述的过滤器生效之后,得到的响应结果如下,入参被修改了,同时返回结果也被修改了

3.2 使用拦截器动态修改请求参数

拦截器在使用上和过滤器有点类似,也是在请求到达接口之前生效,下面直接上代码,参照代码注释进行理解

3.2.1 自定义 HttpServletRequest 包装类 RequestWrapper

因为HttpServletRequest对象的body数据只能get,不能set,即不能再次赋值。而我们的需求是需要给HttpServletRequest赋值,所以需要定义一个HttpServletRequest实现类:customHttpServletRequestWrapper,这个实现类可以被赋值来满足我们的需求。

package com.congge.filter.v3;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {// 保存request body的数据private String body;// 解析request的inputStream(即body)数据,转成字符串public CustomHttpServletRequestWrapper(HttpServletRequest request) {super(request);StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = null;InputStream inputStream = null;try {inputStream = request.getInputStream();if (inputStream != null) {bufferedReader = new BufferedReader(new InputStreamReader(inputStream));char[] charBuffer = new char[128];int bytesRead = -1;while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {stringBuilder.append(charBuffer, 0, bytesRead);}} else {stringBuilder.append("");}} catch (IOException ex) {} finally {if (inputStream != null) {try {inputStream.close();}catch (IOException e) {e.printStackTrace();}}if (bufferedReader != null) {try {bufferedReader.close();}catch (IOException e) {e.printStackTrace();}}}body = stringBuilder.toString();}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());ServletInputStream servletInputStream = new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int read() throws IOException {return byteArrayInputStream.read();}};return servletInputStream;}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));}public String getBody() {return this.body;}// 赋值给body字段public void setBody(String body) {this.body = body;}
}

3.2.2 自定义CustomInterceptor

拦截请求,获取接口方法相关信息(方法名,参数,返回值等)。从而实现统一的给request body动态赋值,实现思路如上所述,具体的实现代码如下:

package com.congge.filter.v3;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.yaml.snakeyaml.util.ArrayUtils;import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;@Slf4j
public class CustomInterceptor implements HandlerInterceptor {static Map<String, Map> urlParamMap = new HashMap();static {Map paramMap = new HashMap();paramMap.put("userName","jerry");urlParamMap.put("/create",paramMap);}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) handler;pushUserInfo2Body(request, handlerMethod);return true;}private void pushUserInfo2Body(HttpServletRequest request, HandlerMethod handlerMethod) throws Exception{//获取请求参数String queryString = request.getQueryString();log.info("请求参数:{}", queryString);//获取请求bodybyte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());String body = new String(bodyBytes, request.getCharacterEncoding());CustomHttpServletRequestWrapper requestWrapper = (CustomHttpServletRequestWrapper) request;JSONObject jsonBody = JSONObject.parseObject(body);//执行参数修改String requestURI = request.getRequestURI();if(urlParamMap.containsKey(requestURI)){Map paramMap = urlParamMap.get(requestURI);paramMap.forEach((key,val)->{if(jsonBody.containsKey(key)){jsonBody.put(String.valueOf(key),paramMap.get(key));}});}requestWrapper.setBody(JSON.toJSONString(jsonBody));}
}

3.2.3 自定义过滤器

自定义UserInfoFilter 过滤器,实现Filter接口,该类的作用是,在进入拦截器之前,将request中的参数封装到自定义的CustomHttpServletRequestWrapper中,以便后续在拦截器中可以对请求参数进行修改;

package com.congge.filter.v3;import lombok.extern.slf4j.Slf4j;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;@Slf4j
public class UserFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {CustomHttpServletRequestWrapper customHttpServletRequestWrapper = null;try {HttpServletRequest req = (HttpServletRequest)request;customHttpServletRequestWrapper = new CustomHttpServletRequestWrapper(req);}catch (Exception e){log.warn("customHttpServletRequestWrapper Error:", e);}chain.doFilter((Objects.isNull(customHttpServletRequestWrapper) ? request : customHttpServletRequestWrapper), response);}
}

3.2.4 请求参数对象

UserInfoParam

import lombok.Data;@Data
public class UserInfoParam {private Long userId;private String userName;
}

3.2.5 测试接口

import lombok.Data;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TemplateController {@PostMapping(value = "/create")public Object create(@RequestBody RequestParam param) {return param;}
}@Data
class RequestParam{private Long templateId;private String userId;private String userName;
}

3.2.6 效果测试

如下请求参数,预期请求接口之后返回的参数被修改掉

四、springboot使用反射+aop实现动态修改请求参数

通过上面的介绍,我们实现了使用过滤器或拦截器对接口请求参数的动态修改效果,整体来说,也是可以满足很多场景下的使用,而且具备一定的通用性,但是认真思考的同学可能会发现,这两种方式的实现,是基于对接口请求的前置操作,在一些高并发场景下,这种方式多少会带来一定的性能上的损耗,并且不够灵活,而且定制化程度不够高,接下来再介绍另一种实现方案,即采用自定义注解+aop的方式实现对特定接口请求参数的修改。

4.1 实现思路

整体实现思路如下:

  • 自定义接口中要修改的参数;

  • 在需要修改的接口上添加自定义注解,补充需要修改的参数;

  • 自定义aop实现,解析自定义注解,利用反射技术动态修改指定的字段,并修改为特定的值;

4.2 代码实现过程

4.2.1 导入aop依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

4.2.2 自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ModifyRequestParams {Param[] value();String requestClassName();@Retention(RetentionPolicy.RUNTIME)@Target({})public static @interface Param {String name();String value();}}

4.2.3 请求对象参数

后续在接口中,将会对里面的参数进行动态修改

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRequest {private String name;private String address;}

4.2.4 自定义aop实现类

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Aspect
@Component
@Order(1) // 设置优先级,数值越小优先级越高
public class RequestParamModifierAspect {private final ObjectMapper objectMapper = new ObjectMapper();@Pointcut("@annotation(com.congge.aop.ModifyRequestParams)")public void pointParam(){}@Around("@annotation(modifyRequestParams)")public Object modifyRequestParams(ProceedingJoinPoint point, ModifyRequestParams modifyRequestParams) throws Throwable {Object result = null;String fullClassName = modifyRequestParams.requestClassName();Object[] argsArray = point.getArgs();List<String> modifyParams = new ArrayList<>();for (ModifyRequestParams.Param param : modifyRequestParams.value()) {modifyParams.add(param.name());}String paramStr = objectMapper.writeValueAsString(argsArray[0]);Map<String, Object> map = JSON.parseObject(paramStr, Map.class);for(Object param : argsArray){Class<?> clazz = param.getClass();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {String name = field.getName();if(modifyParams.contains(name)){field.setAccessible(true);//原来的值Object oldVal = map.get(name);String newVal = oldVal + "_change";field.set(param, newVal);}}}result = point.proceed();return result;}
}

4.2.5 测试接口

添加一个测试接口,接口中使用自定义的注解,如果你的业务中,需要修改更多的请求参数,只需要在注解中添加即可,需要注意的是,注解中的请求参数名称和对象中定义的要保持一致

    //localhost:8081/aop/post/test@PostMapping("/aop/post/test")@ModifyRequestParams(value = {@ModifyRequestParams.Param(name = "address", value = "newValue1"),@ModifyRequestParams.Param(name = "name", value = "newValue2")},requestClassName = "com.congge.aop.UserRequest")public UserRequest testPost(@RequestBody(required = false) UserRequest userRequest) {System.out.println("进入接口");String myParam1 = userRequest.getName();String myParam2 = userRequest.getAddress();System.out.println(myParam1 + ":" + myParam2);return new UserRequest(myParam1,myParam2);//return "Received: " + myParam1 + ", " + myParam2;}

4.2.6 测试效果展示

请求一下上述接口,可以看到参数已经被修改了

4.2.7 扩展补充点

基于上述的实现,在实际业务中,还可以扩展出更丰富的场景,比如为那些默认的为空的参数赋初值,为时间字段根据时区动态赋值等,可以在上面的代码中继续完善。

五、写在文末

本文通过详细的案例操作演示了如何在springboot项目中对接口请求参数进行动态修改,如果在实际使用中,可以基于自身的需求场景酌情使用,并做代码上的持续完善,希望对看到的同学有用,本篇到此结束,感谢观看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/53327.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle rac模式下undo表空间爆满的解决

文章目录 前言一、确认对应实例的undo表空间二、确认对应实例undo的文件位置三、确认回滚段使用情况四、检查undo segment状态五、创建新的undo表空间并进行切换六、等待原undo表空间segment状态变更为offline七、删除原undo表空间以及数据文件 前言 一、确认对应实例的undo表空…

【云原生】Helm来管理Kubernetes集群的详细使用方法与综合应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

Seata环境搭建

1、Seata下载&#xff1a; 1.下载地址 2.下载的版本 2、Seata参数配置参考&#xff1a; 各种seata参数官网参考 3、Seata安装部署&#xff1a; 3.1.Seata新手部署指南: 3.2.在mysql8.0数据库里面建库建表 a.建数据库&#xff1a; create database seata; use seata;b.建…

PVN3D(一)代码框架

在windows上配置pvn3d的环境一直配不成功&#xff0c;主要卡在了与C联合编译上&#xff0c;不知道如何处理了。索性先看看代码&#xff0c;竟然发现与论文中的代码对应上了。希望这一段时间把环境配置好。 1.论文中的网络结构 1.RGB图像特征&#xff0c;通过CNN提取特征。深度…

【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;算法 目录​​​​​​​ 前言 一、三路快排的整体思路 二、三路快排的具体实现 1.测试数据、交换函数和三数取中法 2.三路快排函数 三、程序全部代码 总…

无线麦克风推荐哪些品牌,无线麦克风哪个品牌好,好的麦克风推荐

在声音创作与直播的领域里&#xff0c;无线领夹麦克风作为捕捉清晰声音的第一道防线&#xff0c;其重要性不言而喻。传统的有线麦克风及部分无线产品&#xff0c;难以在移动场景下提供稳定、清晰的录音效果&#xff1b;劣质无线领夹麦克风往往音质不稳定&#xff0c;甚至可能在…

数据库太慢跑崩的一大罪魁

就是非常不起眼的帐号去重计数&#xff0c;用 SQL 写就是 COUNT(DISTINCT …)。 帐号去重计数在商业分析中很常见也有重要的业务意义。这里的帐号可能是用户 ID、银行帐户、手机号、车牌号、…。计算逻辑基本一样&#xff0c;就是从某个时段的历史数据中统计出有多少个帐号满足…

缓存解决方案。Redis 和 Amazon ElastiCache 比较

欢迎来到雲闪世界。Redis 和 Amazon ElastiCache 等缓存解决方案是通过将频繁访问的数据存储在内存中来提高应用程序性能的热门选择。让我们从实施简单性、性能、成本和维护方面对它们进行比较。 实施简单 设置 Redis 需要在基础设施或云实例上安装和配置 Redis 服务器。它可…

解决 Android 上的 .NET MAUI/Xamarin.AndroidX 应用调用 ASP.NET Core API 端点时 SSL 连接被拒绝的问题

从虚拟机调用本地API报各种 SSL 连接不上的错误&#xff0c;这给本地调试造成了极大的不便&#xff0c;在被这个问题困扰了多日以后&#xff0c;终于在GitHub上找到答案 基于这个 帖子 &#xff0c;有一个回复 他写了一个帮助类&#xff0c;专门用来调试本地的API&#xff0c;…

常见的图纸加密软件方式:推荐10个最好用的图纸加密软件

在当今竞争激烈的商业环境中&#xff0c;保护设计图纸等敏感信息已成为企业不可忽视的任务。图纸加密软件作为一项重要的信息安全措施&#xff0c;不仅能够防止未经授权的访问&#xff0c;还能确保设计工作的机密性和完整性。面对市场上琳琅满目的加密软件&#xff0c;选择一款…

变压器结构

变压器结构提供磁路&#xff0c;通常称为“变压器铁芯”&#xff0c;旨在为磁场提供流动路径。该磁路对于两个输入和输出绕组之间感应电压必不可少。 然而&#xff0c;这种变压器结构&#xff08;两个绕组缠绕在不同的支路上&#xff09;效率不高&#xff0c;因为初级绕组和次…

Secret

Secret 在 Kubernetes 中&#xff0c;Secret 是一种用于存储敏感信息的资源&#xff0c;比如密码、OAuth 令牌、SSH 密钥等。Secret 允许你以安全的方式将这些信息传递给 Pod&#xff0c;而不是硬编码在镜像或配置文件中。 配置管理中心&#xff1a;configMap、Secret Secre…

数据库和MySQL

ER图 实体&#xff08;矩形&#xff09;&#xff1a;通常是现实世界的业务对象&#xff0c;当然使用一些逻辑对象也可以。 属性&#xff08;椭圆&#xff09;&#xff1a;实体拥有的属性。 联系&#xff08;菱形&#xff09;&#xff1a;实体与实体之间的关系。 函数依赖 函数依…

使用python+opencv解析图像和文本数据

1. 创建虚拟环境 新建文件夹, 并在文件夹中创建虚拟环境,可以使用Vscode打开文件夹, 然后在终端中输入以下命令: python -m venv venv2. 激活虚拟环境 在终端中输入以下命令: venv\Scripts\activate3. 安装依赖 在终端中输入以下命令: pip install opencv-pythonpip inst…

深度学习示例2-多输入多输出的神经网络模型

一、代码示例 from tensorflow import keras from tensorflow.keras import layers import numpy as np# 定义 多输入 多输出的模型 vocabulary_size = 1000 num_tags = 100 num_departments = 4title = keras.Input(shape=(vocabulary_size,), name = "title") tex…

kali——msfconsole的使用

目录 前言 msfconsole的使用基础 启动mfsconsole 各种辅助模块的使用 端口扫描模块&#xff08;portscan&#xff09; 查看端口扫描的模块 使用模块 查看所需设置 手动设置 执行 退出 各种漏洞模块的使用 nmap扫描主机漏洞 查看漏洞模块 使用漏洞模块 查看所需设…

Prometheus通过node_exporter监控Node节点,Node节点的详细指标解读

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

吴恩达深度学习笔记:卷积神经网络(Foundations of Convolutional Neural Networks)1.9-1.10

目录 第四门课 卷积神经网络&#xff08;Convolutional Neural Networks&#xff09;第一周 卷积神经网络&#xff08;Foundations of Convolutional Neural Networks&#xff09;1.9 池化层&#xff08;Pooling layers&#xff09;1.10 卷 积 神 经 网 络 示 例 &#xff08; …

Linux_kernel汇编驱动06

一、ARM汇编语言&#xff08;GUN-gcc编译器下&#xff09; 1、语句格式 {symbol} {instruction|directive|pseudo-instruction} { comment} symbol&#xff1a;为符号。 在ARM汇编语言中&#xff0c;符号必须从一行的行头开始&#xff0c;并且符号中不能包含空格。 在指令和伪指…

基于C++实现(MFC界面)家谱管理系统

一、题目&#xff1a;家谱管理系统 二、内容&#xff1a; 2.1 概述 2.1.1 选题原因 做此题的原因是因为可以比较方便的记录家族历代成员的情况与关系&#xff0c;能很好的保存家族每一代的信息&#xff0c;而不用人工纸质的方式来存取家谱&#xff0c;更便于人们保存和使用…