在现代Web应用开发中,我们经常需要在请求到达最终处理器之前对请求数据进行动态修改。本文将介绍如何使用Spring的SpEL(Spring Expression Language)表达式来动态修改HTTP请求的参数和请求体,并通过一个完整的示例展示实现过程。
整体架构概述
整个解决方案的核心流程包括:
1. HandlerInterceptor拦截请求
使用Spring的HandlerInterceptor接口来拦截请求,在请求被实际处理前进行预处理:
publicclassSmartRouterInterceptorimplementsHandlerInterceptor{privatefinalSmartRouterManagersmartRouterManager;publicSmartRouterInterceptor(SmartRouterManagersmartRouterManager){this.smartRouterManager=smartRouterManager;}@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{returnsmartRouterManager.rule(request,response);}}SmartRouterInterceptor作为拦截器,将请求交给SmartRouterManager进行规则处理,这是整个流程的入口点。
2. 重新封装HttpServletRequestWrapper以支持请求体反复读取
由于HTTP请求体只能被读取一次,我们需要创建一个可变的请求包装器来支持多次读取和修改:
publicclassMutableHttpServletRequestWrapperextendsHttpServletRequestWrapper{privateMap<String,String[]>modifiedParams=newHashMap<>();privatebyte[]modifiedBody;privateBufferedReaderbodyReader;publicMutableHttpServletRequestWrapper(HttpServletRequestrequest)throwsIOException{super(request);// 复制原始参数modifiedParams.putAll(request.getParameterMap());// 读取并缓存原始请求体if(isRequestBodySupported(request)){InputStreaminputStream=request.getInputStream();modifiedBody=StreamUtils.copyToByteArray(inputStream);bodyReader=newBufferedReader(newInputStreamReader(newByteArrayInputStream(modifiedBody),StandardCharsets.UTF_8));}}@OverridepublicServletInputStreamgetInputStream()throwsIOException{if(modifiedBody==null){returnsuper.getInputStream();}finalByteArrayInputStreambyteArrayInputStream=newByteArrayInputStream(modifiedBody);returnnewServletInputStream(){@OverridepublicbooleanisFinished(){returnbyteArrayInputStream.available()==0;}@OverridepublicbooleanisReady(){returntrue;}@OverridepublicvoidsetReadListener(ReadListenerreadListener){}@Overridepublicintread()throwsIOException{returnbyteArrayInputStream.read();}};}// 其他重写方法...}这个包装器类解决了原生HttpServletRequest只能读取一次请求体的问题,通过缓存机制实现了请求体的重复读取。
3. 使用SpEL表达式修改参数和请求体
核心功能是使用SpEL表达式来动态修改请求数据:
@UtilityClasspublicclassSpelParamModifier{privatestaticfinalExpressionParserPARSER=newSpelExpressionParser();publicvoidmodifyParam(Map<String,String[]>params,StringspelExpression){StandardEvaluationContextcontext=newStandardEvaluationContext();context.setVariable("params",params);PARSER.parseExpression(spelExpression).getValue(context);}publicvoidmodifyJsonBody(Map<String,Object>params,StringspelExpression){StandardEvaluationContextcontext=newStandardEvaluationContext();context.setVariable("params",params);PARSER.parseExpression(spelExpression).getValue(context);}}在SmartRouterManager中集成这些组件:
if(StringUtils.hasText(mapRule)||StringUtils.hasText(bodyRule)){MutableHttpServletRequestWrappermutableRequest=newMutableHttpServletRequestWrapper(request);if(StringUtils.hasText(mapRule)){Map<String,String[]>modifiedMap=newHashMap<>(mutableRequest.getParameterMap());SpelParamModifier.modifyParam(modifiedMap,mapRule);modifiedMap.forEach(mutableRequest::setParameter);}if(StringUtils.hasText(bodyRule)){StringoriginalBody=StreamUtils.copyToString(mutableRequest.getInputStream(),StandardCharsets.UTF_8);Map<String,Object>bodyMap=OBJECT_MAPPER.readValue(originalBody,Map.class);SpelParamModifier.modifyJsonBody(bodyMap,bodyRule);StringmodifiedBody=OBJECT_MAPPER.writeValueAsString(bodyMap);mutableRequest.setJsonBody(modifiedBody);}request=mutableRequest;}实际应用场景
这种设计模式特别适用于以下场景:
- API网关:在请求转发前修改请求参数或请求体
- 路由规则:根据不同条件动态修改请求内容
- 参数验证和清理:在业务逻辑处理前统一修改请求数据
- A/B测试:根据规则动态修改请求参数进行实验
总结
通过结合使用HandlerInterceptor、HttpServletRequestWrapper和SpEL表达式,我们可以创建一个灵活的请求处理系统,能够在运行时动态修改HTTP请求的参数和请求体。这种方案提供了高度的灵活性和可扩展性,使得我们能够根据业务需求编写复杂的请求修改规则。
示例代码地址:
Gitee
Github