SpringMvc请求参数的二次处理
需求
在一个项目中原来服务端是采用WebService
的进行客户端和服务进行交互,现根据需要调整为直接使用HHTP
方式访问。仅修改访问的方式,其数据的访问结构等不变。
尝试
1.HandlerMethodArgumentResolver
- 原数据结构:在旧项目的设计中,
data
数据是采用GBK
的base64
编码,在本次改造中,希望同时将解码的过程也统一处理。
1 2 3 4 5 6 7 8 9 10 11
| public class RequestInfo {
private static final long serialVersionUID = -3033941769749731426L;
private String code;
private String message;
@ApiModelProperty("请求数据(basic64)") private String data; }
|
- 第一时间想到的就是
SpringMvc
的参数处理器HandlerMethodArgumentResolver
。直接上手开干。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Slf4j public class RequestDateParamMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override public boolean supportsParameter(MethodParameter parameter) { return true; }
@Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { log.info("hello"); return null; } }
@Configuration public class WebConfig implements WebMvcConfigurer {
@Bean public RequestDateParamMethodArgumentResolver requestDateParamMethodArgumentResolver(){ return new RequestDateParamMethodArgumentResolver(); }
@Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(requestDateParamMethodArgumentResolver()); } }
|
3.本以为可以完美解决,断点打在resolveArgument
,正想启动后根据断点查询对应的参数信息,在进一步完善代码。测试发现,根本就没有进入断点。
4.虽说对参数解析器的执行过程不是非常的了解,但是大概知道参数解析器的处理是在适配器Adapter
查找对应的HandlerMethod
之后执行,所以根据调用栈,查找关键代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
|
所以:若需要使用参数处理器进行处理,需要自定义一个注解后,标记在方法的属性处,而改造的本意还是希望业务代码层无需关心解码这一行为,若需要在每个方法处都标记一个注解,与初衷相悖。
2. @InitBinder
这种方式是比较细粒度的控制方式,仅控制单个Controller
,在这个Controller
中,的参数会被拦截进行数据的二次处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @RestController @Slf4j public class ArgumentResolverController {
@InitBinder public void initBinder(WebDataBinder webDataBinder){ if(webDataBinder.getTarget() instanceof User){ User user = (User)webDataBinder.getTarget(); log.info(user.getUsername()); user.setPassword("updatePassword"); } }
@RequestMapping("setUserInfo") public String setUserInfo(@RequestBody User user){ log.info("user: {},{}",user.getUsername(), user.getPassword()); return null; } }
|
3. RequestBodyAdvice
经过尝试ArgumentAdvice
可以解决这个问题,通过afterBodyRead
方法可以在参数解析器处理之后获取到对应的解析实体,再根据该实体的类型,进行basic64
解码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
@Component @ControllerAdvice("cn.com.xiaocainiaoya.controller") public class ArgumentAdvice implements RequestBodyAdvice {
@Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return true; }
@Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { return inputMessage; }
@Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { if(body instanceof User){ ((User) body).setPassword("12312"); } return body; }
@Override public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return null; } }
|
小结
虽然最后没有没有通过参数处理器来解决这个问题,但是在测试过程中也发现了自己对参数解析器的理解不足,原以为只要supportsParameter
方法为true
,就可以进行参数处理,而我在完善代码时只需要抽象一个顶层属性接口做是否是某种类型的判断即可。
@InitBinder
和RequestBodyAdvice
都是可取的方案,取决于需求是需全局(某个包下的所有Controller
)的参数都需要进行某种统一处理,或者是仅仅只需针对某一个Controller
进行参数处理。