无须定义类,Spring 快速注入 Json 参数
前言
不知各位在开发的时候有没有遇到这种情况,当前后端分离的时候,前端时常会把很简单的参数使用 JSON 格式传入,当 Spring 要获取这些参数的时候每次都需要定义一个类,在使用的时候也需要使用对象的 Getter
方法,这样极其不方便。而如果要改前端使用 FormData 的方式传输,那么又会遇到另外一个问题:前端常用的请求库是 axios
,而 axios 传输的数据默认是采用 JSON 格式传输的,如果需要使用 FromData 的方式传输,那么需要再每个请求方法上增加 FormData 的 Content-Type
,或者添加到默认的配置中。
那么有什么办法可以在 Spring 中使 JSON 可以像 FormData 那样方便的注入呢?
思路
在 Spring 入参 Controller 的时候会经过一系列的 HandlerMethodArgumentResolver
,我们可以写一个 Resolver 实现该接口,并在 Spring 中增加这个 Resolver,那么只要符合 JSON 格式参数,那么就可以通过该 Resolver 实现注入。
实现
首先我们需要准备一些注解,用于标注是通过 JSON 格式的数据获取参数的:
首先是和 @RequestParam
一样的 @JsonParam
,用于标注该参数注入的名称和是否必须注入等信息:
_14@Target({ ElementType.PARAMETER })_14@Retention(RetentionPolicy.RUNTIME)_14@Documented_14public @interface JsonParam {_14 @AliasFor("name")_14 String value() default "";_14_14 @AliasFor("value")_14 String name() default "";_14_14 boolean required() default true;_14_14 String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";_14}
然后是用于标注在方法上的 @RequestJson
,通过标注这个注解,我们就可以不需要使用 @JsonParam
注解来描述参数,而是使 Spring 通过参数的名称注入该参数:
_5@Target({ ElementType.METHOD })_5@Retention(RetentionPolicy.RUNTIME)_5@Documented_5public @interface RequestJson {_5}
还有一些工具类这里就不写了,这些工具类主要是用于一些特性的实现,比如点语法等。具体可以到 这里 查看。
接着就是用于处理 JSON 格式参数的 HandlerMethodArgumentResolver
了:
_116package me.ixk.json_inject.injector;_116_116import com.fasterxml.jackson.databind.JsonNode;_116import com.fasterxml.jackson.databind.node.NullNode;_116import com.fasterxml.jackson.databind.node.TextNode;_116import java.io.IOException;_116import java.util.stream.Collectors;_116import javax.servlet.http.HttpServletRequest;_116import me.ixk.json_inject.annotation.JsonParam;_116import me.ixk.json_inject.annotation.RequestJson;_116import me.ixk.json_inject.utils.Helper;_116import me.ixk.json_inject.utils.JSON;_116import org.springframework.core.MethodParameter;_116import org.springframework.web.bind.MissingServletRequestParameterException;_116import org.springframework.web.bind.support.WebDataBinderFactory;_116import org.springframework.web.context.request.NativeWebRequest;_116import org.springframework.web.method.support.HandlerMethodArgumentResolver;_116import org.springframework.web.method.support.ModelAndViewContainer;_116_116public class JsonArgumentResolver implements HandlerMethodArgumentResolver {_116 private static final String JSON_REQUEST_ATTRIBUTE_NAME = "JSON_REQUEST_BODY";_116_116 @Override_116 public boolean supportsParameter(final MethodParameter methodParameter) {_116 // 当参数有 JsonParam 或者 RequestJson 注解的时候,就视为传入的是 JSON 格式的参数_116 return (_116 methodParameter.hasParameterAnnotation(JsonParam.class) ||_116 methodParameter.hasMethodAnnotation(RequestJson.class)_116 );_116 }_116_116 @Override_116 public Object resolveArgument(_116 final MethodParameter methodParameter,_116 final ModelAndViewContainer modelAndViewContainer,_116 final NativeWebRequest nativeWebRequest,_116 final WebDataBinderFactory webDataBinderFactory_116 )_116 throws Exception {_116 // 读取并解析 JSON,记得把 JSON 存起来,一是可以避免多次解析,二是 Body 如果没有进行处理的话默认只能读取一次,当第二次读取的时候则会为空。_116 final JsonNode body = this.getJsonBody(nativeWebRequest);_116 // 判断是否是读取整个 JSON (因为有的时候传入的可能是一个数组或者其他类型的数据,而不是 JSON 对象)_116 if ("$body".equals(methodParameter.getParameterName())) {_116 return JSON.convertToObject(body, methodParameter.getParameterType());_116 }_116 final JsonParam jsonParam = methodParameter.getParameterAnnotation(_116 JsonParam.class_116 );_116 final RequestJson requestJson = methodParameter.getMethodAnnotation(_116 RequestJson.class_116 );_116 JsonNode node = NullNode.getInstance();_116 if (jsonParam != null) {_116 // 通过 JsonParam 中设置的名称读取_116 node = Helper.dataGet(body, jsonParam.name(), NullNode.getInstance());_116 } else if (requestJson != null) {_116 // 通过参数名称读取_116 node = body.get(methodParameter.getParameterName());_116 }_116 if (node.isNull()) {_116 if (jsonParam != null) {_116 // 若为空并且必须传入,则抛出异常_116 if (jsonParam.required()) {_116 throw new MissingServletRequestParameterException(_116 jsonParam.name(),_116 methodParameter.getParameterType().getTypeName()_116 );_116 } else {_116 // 否则传入默认参数_116 node = TextNode.valueOf(jsonParam.defaultValue());_116 }_116 } else {_116 return null;_116 }_116 }_116 // 转换数据类型_116 return JSON.convertToObject(node, methodParameter.getParameterType());_116 }_116_116 private JsonNode getJsonBody(final NativeWebRequest nativeWebRequest) {_116 final HttpServletRequest servletRequest = nativeWebRequest.getNativeRequest(_116 HttpServletRequest.class_116 );_116_116 JsonNode body = (JsonNode) nativeWebRequest.getAttribute(_116 JSON_REQUEST_ATTRIBUTE_NAME,_116 NativeWebRequest.SCOPE_REQUEST_116 );_116_116 if (body == null) {_116 try {_116 if (servletRequest != null) {_116 body =_116 JSON.parse(_116 servletRequest_116 .getReader()_116 .lines()_116 .collect(Collectors.joining("\n"))_116 );_116 }_116 } catch (final IOException e) {_116 //_116 }_116 if (body == null) {_116 body = NullNode.getInstance();_116 }_116 nativeWebRequest.setAttribute(_116 JSON_REQUEST_ATTRIBUTE_NAME,_116 body,_116 NativeWebRequest.SCOPE_REQUEST_116 );_116 }_116_116 return body;_116 }_116}
最后还需要将该 Resolver 添加到 Spring 中:
_10@Configuration_10public class WebConfig implements WebMvcConfigurer {_10_10 @Override_10 public void addArgumentResolvers(_10 List<HandlerMethodArgumentResolver> resolvers_10 ) {_10 resolvers.add(new JsonArgumentResolver());_10 }_10}
至此实现的部分就完成了,让我们看看如何使用吧。
使用
_46@SpringBootApplication_46@RestController_46public class JsonInjectApplication {_46_46 public static void main(final String[] args) {_46 SpringApplication.run(JsonInjectApplication.class, args);_46 }_46_46 @PostMapping("/param")_46 public String param(@JsonParam(name = "key") final String key) {_46 return key;_46 }_46_46 @PostMapping("/method")_46 @RequestJson_46 public String method(final String key) {_46 return key;_46 }_46_46 @PostMapping("/data-get")_46 public String dataGet(@JsonParam(name = "key.sub") final String key) {_46 return key;_46 }_46_46 @PostMapping("/default-value")_46 public String defaultValue(_46 @JsonParam(name = "value", required = false) final String value_46 ) {_46 return value;_46 }_46_46 @PostMapping("/convert")_46 public Integer convert(@JsonParam(name = "key") final Integer key) {_46 return key;_46 }_46_46 @GetMapping("/get")_46 public String get(String key) {_46 return key;_46 }_46_46 @PostMapping("/post")_46 public String post(String key) {_46 return key;_46 }_46}
可以看到使用的方式和 @RequestParam
无太大差别。
结语
最近打算开始接着折腾 Spring 和后端了,感觉我还是更喜欢后端,前端太折腾了 ?。
无须定义类,Spring 快速注入 Json 参数
https://blog.ixk.me/post/spring-quickly-inject-json-parameters许可协议
发布于
2020-08-04
本文作者
Otstar Lin
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!