Outlook is introduced

In our daily development process, the foreground usually transfers parameters to the background in JSON or form form mode, and it is better to adopt one transmission mode. Json and form are not recommended to be used in combination. However, as form forms are difficult to join together under the array structure, JSON is generally used for transmission. The back end uses SpringBoot (SpringMVC) and generally uses the @RequestBody annotation to receive data, but the following situations are not easy to handle

@RequestMapping("/login")
public String login(String name,String password){... }Copy the code

The above method cannot be mapped if it receives json objects. Since there are too few fields to encapsulate as objects + @requestBody is too cumbersome, we can use custom annotations + method parameter parsers to solve this problem

Import dependencies

Framework is used in the Hutool toolkit, according to their own projects using JSON serialization tools

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.6</version>
        </dependency>
Copy the code

Define annotations

Annotate parameters that need to be mapped using custom annotations

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JSONParameter {
    @AliasFor("name")
    String value(a) default "";

    @AliasFor("value")
    String name(a) default "";

    boolean required(a) default false;

    String defaultValue(a) default "";
}


Copy the code

Write a custom parameter parser

package com.gosun.swd.commom.resolver;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.gosun.swd.commom.annotation.JSONParameter;
import com.gosun.swd.commom.exception.Assert;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;

@Component
public class JSONParameterResolver implements HandlerMethodArgumentResolver {

// ThreadLocal
      
        paramObject = new ThreadLocal<>();
      


    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.hasParameterAnnotation(JSONParameter.class);
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        JSONObject jsonObject = getJsonObject();
        Object o = jsonObject.get(methodParameter.getParameterName());
        JSONParameter parameterAnnotation = methodParameter.getParameterAnnotation(JSONParameter.class);
        // Determine whether null is allowed
        String message = String.format("Parameter %s cannot be empty", methodParameter.getParameterName());
        Assert.assertFalse(parameterAnnotation.required() && o == null, message);
        // Set the default value if null
        if (o == null) {
            return convertParameter(methodParameter, parameterAnnotation.defaultValue());
        }
        return convertParameter(methodParameter, o);
    }

    private Object convertParameter(MethodParameter methodParameter, Object o) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
        if (methodParameter.getParameterType().getTypeName().equals(List.class.getTypeName()) ||
                methodParameter.getParameterType().getTypeName().equals(Set.class.getTypeName())) {
            Type actualTypeArgument = ((ParameterizedTypeImpl) methodParameter.getGenericParameterType()).getActualTypeArguments()[0];
            return JSONUtil.toList((JSONArray) o, Class.forName(actualTypeArgument.getTypeName()));
        } else {
            returnconvertParameter(methodParameter, String.valueOf(o)); }}private Object convertParameter(MethodParameter methodParameter, String obj) throws InstantiationException, IllegalAccessException, InvocationTargetException { Constructor<? >[] constructors = methodParameter.getParameterType().getConstructors();for(Constructor<? > constructor : constructors) {if (constructor.getParameterCount() == 1 && constructor.getParameters()[0].getParameterizedType().getTypeName().equals(String.class.getTypeName())) {
                returnconstructor.newInstance(obj); }}return null;
    }

    private JSONObject getJsonObject(a) throws IOException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
        StringBuilder responseStrBuilder = new StringBuilder();
        String inputStr;
        while((inputStr = streamReader.readLine()) ! =null) {
            responseStrBuilder.append(inputStr);
        }
        String jsonStr = responseStrBuilder.toString();
// if (stringutils.isempty (jsonStr)) {// If there is no argument, it is not the first time to enter
// return paramObject.get() == null ? new JSONObject() : paramObject.get();
/ /}
        JSONObject jsonObject = JSONUtil.parseObj(jsonStr);
// paramObject.set(jsonObject);
        returnjsonObject; }}Copy the code

Solve the problem that the request information is lost after obtaining the request information twice

1. Write filters

package com.gosun.swd.commom.resolver;


import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/ * * *@author: * /
@Component
@WebFilter(filterName = "channelFilter", urlPatterns = {"/*"})
public class ChannelFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        try {
            ServletRequest requestWrapper = null;
            if (request instanceof HttpServletRequest) {
                requestWrapper = new RequestWrapper((HttpServletRequest) request);
            }
            if (requestWrapper == null) {
                chain.doFilter(request, response);
            } else{ chain.doFilter(requestWrapper, response); }}catch (IOException e) {
            e.printStackTrace();
        } catch(ServletException e) { e.printStackTrace(); }}@Override
    public void destroy(a) {}}Copy the code

2. Write the constructor

package com.gosun.swd.commom.resolver;

import org.apache.commons.io.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/ * * *@author: * /
public class RequestWrapper extends HttpServletRequestWrapper {
    // Parameter byte array
    private byte[] requestBody;
    //Http request object
    private HttpServletRequest request;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.request = request;
    }

    / * * *@return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream(a) throws IOException {
        /** * Each time this method is called, it reads data from the data stream and then backfills it into the InputStream@RequestBodyand@RequestParam(POST mode) The controller cannot get parameters after reading the data once */
        if (null= =this.requestBody) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(), baos);
            this.requestBody = baos.toByteArray();
        }

        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {

            @Override
            public boolean isFinished(a) {
                return false;
            }

            @Override
            public boolean isReady(a) {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {}@Override
            public int read(a) {
                returnbais.read(); }}; }public byte[] getRequestBody() {
        return requestBody;
    }

    @Override
    public BufferedReader getReader(a) throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream())); }}Copy the code

Call instance code


	@RequestMapping("/login")
	public String login(@JSONParameter String name, @JSONParameter String password){... }// Parameter mapping with default values
    @PostMapping(value = "/getEvents", consumes = "application/json")
    public ResultVo<Events> getEvents(@JSONParameter(name = "page", defaultValue = "1") Long page, 
                                      @RequestParam(name = "per_page", defaultValue = "10") Long per_page, 
                                      @JSONParameter String type) {... }// Set of fields
    @PostMapping(value = "/deleteCarInfo", consumes = "application/json")
    public ResultVo deleteCarInfo(@JSONParameter List<Long> ids) {... }Copy the code