Recently, I encountered the situation that multiple @ requestbodies are required in the Controller, but I found that this writing method is not supported,
This leads to
1. A single string and other packing types need to write an object before they can be received with @ RequestBody;
2. Multiple objects need to be encapsulated in one object before they can be received with @ RequestBody.
Referring to StackOverFlow, inspired by a solution, I improved it to the following version and gave detailed comments, hoping to be helpful to you.
Improved program support:
1. Support parsing objects by specifying JSON key through annotated value.
2. It supports parsing objects directly according to parameter names through annotation without value
3. Support GET mode and other request modes
4. Support basic type attribute injection
5. Support parsing the object according to the attribute when the annotation has no value and the parameter name does not match the JSON string key.
6. Support redundant attributes (no parsing, no error reporting), support parameter "sharing" (when value is not specified, the parameter name is not the key of JSON string)
7. Support whether the object matches all attributes when the value and attribute name cannot find a matching key.
Important update records:
New xml mode reference configuration on February 25, 2019
February 7, 2019 fix when the list parameter is empty, parametertype Newinstance causes an exception.
On December 28, 2018, new test cases were added to improve the analysis part of the code
Improve the project format on October 23, 2018
The first edition was created on August 28, 2018
The project is for reference only. Please be responsible for any problems caused by improper use. If there are problems, please discuss and improve them.
Project address (it is recommended to pull the latest code):
https://github.com/chujianyun/Spring-MultiRequestBody
In addition, the code should be continuously updated and improved as much as possible. You are welcome to contribute code.
The steps are as follows:
0. Main Maven dependencies involved except the Jar package of spring
commons-lang commons-lang 2.4 com.alibaba fastjson 1.2.35 commons-io commons-io 2.6
fastjson is used to parse json objects, commons Lang is used to empty the string (you can also write by yourself), and Commons IO is used to encapsulate the read request as a string type (you can also encapsulate by yourself).
1. Rewrite method parameter parser
package com.chujianyun.web.bean; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import io.github.chujianyun.annotation.MultiRequestBody; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Set; /** * Multi RequestBody parser * * @author As bright as the moon * @date 2018/08/27 */ public class MultiRequestBodyArgumentResolver implements HandlerMethodArgumentResolver { private static final String JSONBODY_ATTRIBUTE = "JSON_REQUEST_BODY"; /** * Set supported method parameter types * * @param parameter Method parameters * @return Supported types */ @Override public boolean supportsParameter(MethodParameter parameter) { // Parameters with @ MultiRequestBody annotation are supported return parameter.hasParameterAnnotation(MultiRequestBody.class); } /** * Parameter analysis, using fastjson * Note: if a non basic type returns null, it will report a null pointer exception. Create a null object through reflection or JSON tool class */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String jsonBody = getRequestBody(webRequest); JSONObject jsonObject = JSON.parseObject(jsonBody); // According to the @ MultiRequestBody annotation, value is used as the key parsed by json MultiRequestBody parameterAnnotation = parameter.getParameterAnnotation(MultiRequestBody.class); //The value of the annotation is the key of JSON String key = parameterAnnotation.value(); Object value; // If the @ MultiRequestBody annotation does not set value, the parameter name FrameworkServlet is taken as the key of json parsing if (StringUtils.isNotEmpty(key)) { value = jsonObject.get(key); // If value is set but cannot be parsed, an error is reported if (value == null && parameterAnnotation.required()) { throw new IllegalArgumentException(String.format("required param %s is not present", key)); } } else { // If the annotation is set to value, the parameter name is used as the key of json key = parameter.getParameterName(); value = jsonObject.get(key); } // Gets the type Long after the annotation Class parameterType = parameter.getParameterType(); // By parsing the annotated value or parameter name, you can get the value for parsing if (value != null) { //Basic type if (parameterType.isPrimitive()) { return parsePrimitive(parameterType.getName(), value); } // Basic type packing class if (isBasicDataTypes(parameterType)) { return parseBasicTypeWrapper(parameterType, value); // String type } else if (parameterType == String.class) { return value.toString(); } // Other complex objects return JSON.parseObject(value.toString(), parameterType); } // If it cannot be resolved, the whole json string will be resolved to the current parameter type if (isBasicDataTypes(parameterType)) { if (parameterAnnotation.required()) { throw new IllegalArgumentException(String.format("required param %s is not present", key)); } else { return null; } } // If it is not a basic type, it is not allowed to parse all fields. If it is a required parameter, an error will be reported, and if it is not a required parameter, null will be returned if (!parameterAnnotation.parseAllFields()) { // If it is a required parameter, throw an exception if (parameterAnnotation.required()) { throw new IllegalArgumentException(String.format("required param %s is not present", key)); } // Otherwise, null is returned return null; } // It is not a basic type. It can be parsed and the outer attribute can be parsed Object result; try { result = JSON.parseObject(jsonObject.toString(), parameterType); } catch (JSONException jsonException) { // TODO:: is it reasonable for exception handling to return null? result = null; } // If unnecessary parameters are returned directly, otherwise, if no attribute has a value, an error will be reported if (!parameterAnnotation.required()) { return result; } else { boolean haveValue = false; Field[] declaredFields = parameterType.getDeclaredFields(); for (Field field : declaredFields) { field.setAccessible(true); if (field.get(result) != null) { haveValue = true; break; } } if (!haveValue) { throw new IllegalArgumentException(String.format("required param %s is not present", key)); } return result; } } /** * Basic type resolution */ private Object parsePrimitive(String parameterTypeName, Object value) { final String booleanTypeName = "boolean"; if (booleanTypeName.equals(parameterTypeName)) { return Boolean.valueOf(value.toString()); } final String intTypeName = "int"; if (intTypeName.equals(parameterTypeName)) { return Integer.valueOf(value.toString()); } final String charTypeName = "char"; if (charTypeName.equals(parameterTypeName)) { return value.toString().charAt(0); } final String shortTypeName = "short"; if (shortTypeName.equals(parameterTypeName)) { return Short.valueOf(value.toString()); } final String longTypeName = "long"; if (longTypeName.equals(parameterTypeName)) { return Long.valueOf(value.toString()); } final String floatTypeName = "float"; if (floatTypeName.equals(parameterTypeName)) { return Float.valueOf(value.toString()); } final String doubleTypeName = "double"; if (doubleTypeName.equals(parameterTypeName)) { return Double.valueOf(value.toString()); } final String byteTypeName = "byte"; if (byteTypeName.equals(parameterTypeName)) { return Byte.valueOf(value.toString()); } return null; } /** * Basic type wrapper class parsing */ private Object parseBasicTypeWrapper(Class parameterType, Object value) { if (Number.class.isAssignableFrom(parameterType)) { Number number = (Number) value; if (parameterType == Integer.class) { return number.intValue(); } else if (parameterType == Short.class) { return number.shortValue(); } else if (parameterType == Long.class) { return number.longValue(); } else if (parameterType == Float.class) { return number.floatValue(); } else if (parameterType == Double.class) { return number.doubleValue(); } else if (parameterType == Byte.class) { return number.byteValue(); } } else if (parameterType == Boolean.class) { return value.toString(); } else if (parameterType == Character.class) { return value.toString().charAt(0); } return null; } /** * Judge whether it is a basic data type packing class */ private boolean isBasicDataTypes(Class clazz) { Set classSet = new HashSet<>(); classSet.add(Integer.class); classSet.add(Long.class); classSet.add(Short.class); classSet.add(Float.class); classSet.add(Double.class); classSet.add(Boolean.class); classSet.add(Byte.class); classSet.add(Character.class); return classSet.contains(clazz); } /** * Get request body JSON string */ private String getRequestBody(NativeWebRequest webRequest) { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); // Get it directly if you have it String jsonBody = (String) webRequest.getAttribute(JSONBODY_ATTRIBUTE, NativeWebRequest.SCOPE_REQUEST); // Read from request without if (jsonBody == null) { try { jsonBody = IOUtils.toString(servletRequest.getReader()); webRequest.setAttribute(JSONBODY_ATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST); } catch (IOException e) { throw new RuntimeException(e); } } return jsonBody; } }
2. Write method notes for parsing:
package com.chujianyun.web.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Controller Method in receives multiple JSON objects * * @author As bright as the moon * @date 2018/08/27 */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface MultiRequestBody { /** * Required parameter */ boolean required() default true; /** * Is it allowed to resolve the outermost attribute to the object when the value or parameter name of value do not match */ boolean parseAllFields() default true; /** * JSON used when parsing key */ String value() default ""; }
3. Inject in configuration Bean
Special note: if adding this configuration leads to page access 404, the @ EnableWebMvc annotation can be removed
package com.chujianyun.web.config; import com.chujianyun.web.bean.MultiRequestBodyArgumentResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.nio.charset.Charset; import java.util.List; /** * Add multi RequestBody parser * @author As bright as the moon * @date 2018/08/27 */ @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(List argumentResolvers) { argumentResolvers.add(new MultiRequestBodyArgumentResolver()); } @Bean public HttpMessageConverter responseBodyConverter() { return new StringHttpMessageConverter(Charset.forName("UTF-8")); } @Override public void configureMessageConverters(List> converters) { super.configureMessageConverters(converters); converters.add(responseBodyConverter()); } }
xml configuration mode (thanks for the xml reference configuration mode provided by the netizen "lava")
application/json text/html text/plain
usage method:
package com.chujianyun.web.controller; import com.chujianyun.web.annotation.MultiRequestBody; import com.chujianyun.web.domain.Dog; import com.chujianyun.web.domain.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * Demo controller * @author As bright as the moon * @date 2018/08/27 */ @Controller @RequestMapping("/xhr/test") public class DemoController { @RequestMapping("/demo") @ResponseBody public String multiRequestBodyDemo1(@MultiRequestBody Dog dog, @MultiRequestBody User user) { System.out.println(dog.toString()+user.toString()); return dog.toString()+";"+user.toString(); } @RequestMapping("/demo2") @ResponseBody public String multiRequestBodyDemo2(@MultiRequestBody("dog") Dog dog, @MultiRequestBody User user) { System.out.println(dog.toString()+user.toString()); return dog.toString()+";"+user.toString(); } @RequestMapping("/demo3") @ResponseBody public String multiRequestBodyDemo3(@MultiRequestBody("dog") Dog dog, @MultiRequestBody("user") User user) { System.out.println(dog.toString()+user.toString()); return dog.toString()+";"+user.toString(); } @RequestMapping("/demo4") @ResponseBody public String multiRequestBodyDemo4(@MultiRequestBody("dog") Dog dog, @MultiRequestBody Integer age) { System.out.println(dog.toString() + age.toString()); return dog.toString() + ";age The attributes are:"+age.toString(); } @RequestMapping("/demo5") @ResponseBody public String multiRequestBodyDemo5(@MultiRequestBody("color") String color, @MultiRequestBody("age") Integer age) { return "color="+color + "; age=" + age; } @RequestMapping("/demo6") @ResponseBody public String multiRequestBodyDemo6(@MultiRequestBody("dog") Dog dog, @MultiRequestBody Integer age) { System.out.println(dog.toString() + age.toString()); return dog.toString() + ";age The attributes are:"+age.toString(); } @RequestMapping("/demo7") @ResponseBody public String multiRequestBodyDemo7(@MultiRequestBody Dog color2, @MultiRequestBody("age") Integer age) { return "color="+color2 + "; age=" + age; } @RequestMapping("/demo9") @ResponseBody public String multiRequestBodyDemo9( @MultiRequestBody Dog dog) { return dog.toString(); } @RequestMapping("/demo10") @ResponseBody public String multiRequestBodyDemo10( @MultiRequestBody(parseAllFields = false,required = false) Dog dog) { return dog.toString(); } @RequestMapping("/testList") @ResponseBody public String multiRequestBodyDemo1(@MultiRequestBody List test, @MultiRequestBody String str) { return test.toString() + str; } }
Two entities:
package com.chujianyun.web.domain; /** * @author As bright as the moon * @date 2018/08/27 */ public class Dog { private String name; private String color; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", color='" + color + '\'' + '}'; } }
package com.chujianyun.web.domain; /** * @author As bright as the moon * @date 2018/08/27 */ public class User { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
effect:
demo
demo2
demo3
Reference article: https://stackoverflow.com/questions/12893566/passing-multiple-variables-in-requestbody-to-a-spring-mvc-controller-using-ajax