场景二:
在微服务场景中有个特别常见的现象:跟第三方服务做对接时(如python
老系统),你不乏会遇到如下两个痛点:
- 对方系统是以下划线形式命名的(和Java命名规范相悖)
- 对方系统的参数json串层次较深,而对你有用的仅仅是深处的一小部分
例如这个参数串:
{ "data": { "transport_data": { "demo_name": "fsx", "demo_age": 18 }, "secret_info": { "code": "fkldshjfkldshj" } }, "code": "200", "msg": "this is a message" }
对你真正有用的只有demo_name和demo_age两个值,怎么破???
我相信绝大部分小伙伴都这么做:按照此结构先定义一个DTO全部接收回来(字段命名也用下划线方式命名),然后再一个个处理。若这么做虽然简单,我觉得还是有如下两个不妥的地方:
Java属性名也必须用下划线命名,看起来影响了命名体系(其实就是看着不爽,哈哈)
按照参数这种复杂结构书写,使得我们关注点分散,不能聚焦到真真关心的那一块数据上
针对这些痛点,废话不多说,直接上我的处理方案:
1、定义一个模型(只写我自己关注的属性)
@Getter @Setter @ToString public class TranUserVo { private String demoName; private Long demoAge; }
定义的模型非常之简单,不仅只关心我要的数据,而且还是标准的java驼峰命名,没必要去迁就别的语言而丧失自己优雅性,否则容易把自己弄得四不像(万一又接python,又接.net呢?)~
2、自定义一个参数解析器并且注册上去
public class TranUserArgumentResolver implements HandlerMethodArgumentResolver { // 只处理这个类型,不需要注解 @Override public boolean supportsParameter(MethodParameter parameter) { Class<?> parameterType = parameter.getParameterType(); return TranUserVo.class.isAssignableFrom(parameterType); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod()); // 本例为了简单,演示get的情况(这里使用key为:demoKey) if (httpMethod == HttpMethod.GET) { String value = request.getParameter("demoKey"); JSONObject transportData = (JSONObject) ((JSONObject) JSON.parseObject(value).get("data")).get("transport_data"); // 采用命名策略,转换TranUserVo实例对象再返回 // 序列化配置对象 ParserConfig config = new ParserConfig(); config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; TranUserVo tranUserVo = transportData.toJavaObject(TranUserVo.class, config, 0); return tranUserVo; } else { // 从body提里拿 // ... return null; } } } // 注册此自定义的参数解析器 @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new TranUserArgumentResolver()); }
对此部分我说明一点:对于json到对象的解析,理应还加上@Valid
校验的能力的,此部分我就省略了,毕竟也不是本文所关心的重点
测试用例:
@ResponseBody @GetMapping("/test/tranuser") public Object testCurrUser(TranUserVo tranUser) { return tranUser; }
请求:/test/tranuser?demoKey=上面那一大长串json串,得到的结果就是预期的结果喽:
完美~
说明:这种长传现在需要使用post/put传递,本文只是为了简化演示,所以使用了GET请求,毕竟解析Body体不是本文所需讨论的~
总结
我认为,自定义参数解析器HandlerMethodArgumentResolver最重要不是它本身的实现,而是它的指导思想:分离关注,业务解耦。当然本文我摘出来的两个使用场景案例只是冰山一角,各位需要举一反三,才能融会贯通。
既然我们可以自定义参数处理器HandlerMethodArgumentResolver,自然也就可以自定义返回值处理器HandlerMethodReturnValueHandler喽,作为课后作业,有兴趣者不妨一试,还是非常有作用的。特别在处理"老项目"的兼容性上非常好使,或许能让你大放异彩~