SpringBoot:自定义注解实现后台接收Json参数
阅读原文时间:2023年07月08日阅读:2

在实际的开发过程中,服务间调用一般使用Json传参的模式,SpringBoot项目无法使用@RequestParam接收Json传参

只有@RequestBody支持Json,但是每次为了一个接口就封装一次实体类比较麻烦

如果使用Map来进行参数接收,则会导致参数不可控,会在接口中新增较多判断进行入参控制

其次,在实际的开发过程中,我们偶尔会传入两个实体类,如果使用@RequestBody也会出错

因为传入的参数只能够读取一次,一般这里也会封装一次实体类,不够方便

也有重写HttpServletRequestWrapper的处理办法,但不能解决上一个问题

因为一个注解只能读取一次,按照重写HttpServletRequestWrapper的思路,将请求中的Json参数进行缓存

另外自定义一个注解,来把参数进行注入。

1.1.自定义@JsonFmt注解

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonFmt {
/**
* 值
*/
String value() default "";

/\*\*  
 \* 是否必须  
 \*/  
boolean require() default true;  

}

这里的值,不是给参数的默认值(defaultValue),而是类似于@RequestParam注解中的value、name,是用来指定入参的key

1.2.自定义注解的实现类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.util.StringUtils;
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.BufferedReader;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class JsonFmtHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

//自定义key  
private static final String KEY = "TEST\_JSON\_BODY\_KEY";  
private static ObjectMapper objectMapper = new ObjectMapper();

@Override  
public boolean supportsParameter(MethodParameter parameter) {  
    return parameter.hasParameterAnnotation(JsonFmt.class);  
}

@Override  
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {  
    JsonFmt jsonFmt = parameter.getParameterAnnotation(JsonFmt.class);  
    JSONObject jsonObject = getJsonObject(webRequest);

    String value = getParamName(parameter,jsonFmt);  
    boolean require = jsonFmt.require();  
    Object paramValue = getParamValue(jsonObject,value);

    if (paramValue == null && require) {  
        throw new Exception("parameter\[" + value + "\]不能为空。");  
    }  
    if (paramValue == null) {  
        return null;  
    }

    Class<?> classType = parameter.getParameterType();

    if (paramValue.getClass().equals(JSONObject.class)){  
        paramValue = objectMapper.readValue(paramValue.toString(),classType);  
    }

    return paramValue;  
}

private String getParamName(MethodParameter parameter, JsonFmt jsonFmt) {  
    String value = jsonFmt.value();  
    if (StringUtils.isEmpty(value)) {  
        value = parameter.getParameterName();  
    }  
    return value;  
}

private Object getParamValue(JSONObject jsonObject,String value) {  
    for (String key: jsonObject.keySet()) {  
        if(key.equalsIgnoreCase(value)){  
            return jsonObject.get(key);  
        }  
    }  
    return null;  
}

private JSONObject getJsonObject(NativeWebRequest webRequest) throws Exception {  
    String jsonBody = (String) webRequest.getAttribute(KEY, NativeWebRequest.SCOPE\_REQUEST);  
    if(StringUtils.isEmpty(jsonBody)){  
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);  
        BufferedReader reader = request.getReader();  
        StringBuilder sb = new StringBuilder();  
        char\[\] buf = new char\[1024\];  
        int rd;  
        while ((rd = reader.read(buf)) != -1) {  
            sb.append(buf, 0, rd);  
        }

        jsonBody = sb.toString();

        if(StringUtils.isEmpty(jsonBody)){  
            Map<String,String\[\]> params = request.getParameterMap();

            Map tmp = new HashMap();  
            for (Map.Entry<String,String\[\]> param:params.entrySet()) {  
                if(param.getValue().length == 1){  
                    tmp.put(param.getKey(),param.getValue()\[0\]);  
                }else{  
                    tmp.put(param.getKey(),param.getValue());  
                }

            }  
            jsonBody = JSON.toJSONString(tmp);  
        }

        webRequest.setAttribute(KEY, jsonBody, NativeWebRequest.SCOPE\_REQUEST);  
    }

    return JSONObject.parseObject(jsonBody);  
}  

}

方法说明:

supportsParameter:说明支持的注解,只要方法参数有@JsonFmt就启用该实现类

resolveArgument:解决方法,注解的具体实现

getJsonObject:获取请求体,这里的实现逻辑就是从请求中获取Json体,如果没有获取到,则从请求参数中获取(兼容From模式),将请求体封装为JsonObject

getParamName:获取注解参数的key,先获取注解的value,如果为空,则使用方法参数的名称

getParamValue:这个可以不加,我这里是为了让key不区分大小写,如果需要区分,直接使用jsonObject.get(key)即可

1.3.加入自定义注解

import com.example.demo.jsonfmt.JsonFmtHandlerMethodArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class AppConfig implements WebMvcConfigurer {

@Override  
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {  
    resolvers.add(new JsonFmtHandlerMethodArgumentResolver());  
}

}

到这里我们就能愉快的使用我们的自定义注解@JsonFmt来进行参数接收了

目前在Json传参中,能完美的接收实体类、List、Map以及其他基础类型

在Form传参中,能够支持List、Map以及其他基础类型,对于实体类暂时还不能兼容

因为后台接收到的是Map,不容易区分哪些是实体类的字段,无法进行填充,这种建议使用@RequestBody