引入

在springboot中,我们可以用@RequestBody获取请求中的json数据。但是,我们平常会客制化请求的JSON数据。通常,数据可能是这样:

{
    "header: {
        "deviceId": "deviceId",
        "sessionId": "sessionId"
    }
    "body": {
        "userId": "mycano",
        "password": "mycano"
    }
}

这里,header中会存放用户的相关信息,比如sessionId等内容,而body中则会存放前后端交互的具体数据。 若在此情况下,如果我们使用上述的客制化json数据,那么存在@RequestBody无法获取数据的情况。

比如,当我的请求数据为:

{
    "userId": "123112",
    "password": "odadasfgwf"
}

可以看到,在图片中我们获取到post请求传送给后端的数据。 alt 而当我们的请求数据为:

{
    "body":{
        "userId": "123112",
        "password": "odadasfgwf"
    }
}

此时,我们通过@RequestBody是无法获取到数据的,具体如下图所示。 alt

为了解决这个问题,我们可以通过定制化基本的接收数据的DTO,在DTO中通过反射赋值,便能获取到前端的数据。

解决过程

通过反射获取参数的值,我们需要完成以下几步:

  • 提前获取Request中的数据流,并保存数据流。这个过程可以在Filter中完成,也可以在interceptor中完成,具体看项目的需要。其中filter内完成可以参考https://blog.nowcoder.net/n/8dc8fb4c78994d30a6439125d1c78001。
  • 客制化基本的RequestDTO类,该类需要所有的入参类继承。并在RequestDTO中完成请求参数的赋值。

在这里,我们重点讲如何客制化基本的RequestDTO。首先,我们在前面已经获取了数据流,并将数据流的结果保存在request的BODY属性中。因此,在这里我们会获取这个数据,并通过反射获取。

@Getter
@Setter
public class RequestDTO implements Serializable {

    protected HttpServletRequest request;

    public RequestDTO(){
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if(null != requestAttributes){
            request = requestAttributes.getRequest();
        }else {
            request = null;
            return;
        }
        Object body = request.getAttribute(RequestConstant.BODY);
        if(null != body){
            RequestUtils.invoke(this, body.toString());
        }
    }
}

其中,我们的RequestUtils.invoke(this, body.toString());是在RequestUtils类中,具体为:

public class RequestUtils{
    /**
     * 通过反射对DTO中的变量赋值,如果变量为类下的类,则该类需要用public static修饰。
     * @param object
     * @param body
     * @throws AppTransException
     */
    public static void invoke(Object object, String body) throws AppTransException {
        Map<String, Object> paramsMap = JSONObject.parseObject(body, Map.class);
        Class<?> clz = object.getClass();
        for (Map.Entry<String, Object> entry: paramsMap.entrySet()){
            String key = entry.getKey();
            Object value = entry.getValue();
            Field field;
            try {
                field = clz.getDeclaredField(key);
            }catch (NoSuchFieldException e){
                log.info("no field: {}", key);
                continue;
            }
            Method method;
            String methodName = null;
            try {
                methodName = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
                method = clz.getDeclaredMethod(methodName, field.getType());
            }catch (NoSuchMethodException e){
                log.info("no method: {}", methodName);
                continue;
            }
            try {
                if(value instanceof Map){
                    Class aClass = Class.forName(field.getType().getName());
                    Constructor constructor = aClass.getConstructor();
                    Object o = constructor.newInstance();
                    invoke(o, value.toString());
                    method.invoke(object, o);
                }else {
                    method.invoke(object, value);
                }
            }catch (InvocationTargetException | IllegalAccessException e){
                log.error("method {} can not invoke.", methodName);
            } catch (Exception e) {
                String errorMsg = "类" + object.getClass().getName() + "." + key + "需要由public static修饰,否则无法反射";
                log.error(errorMsg);
                throw new AppTransException(ErrorEnum.CODING_ERROR, errorMsg);
            }
        }
    }
}

在修改完项目后,我们可以测试下。其中请求的数据如下:

{
    "body":{
        "userId": "123112",
        "password": "odadasfgwf"
    }
}

在controller中,我们也获取到了请求的数据: alt