SpringBoot自定义注解实现Post接收单个参数值

前言

由于 Spring 没有提供类似于 @RequestParam 注解,对单个参数的 Post 请求数据进行绑定的注解,所以,如果遇到某些情况会格外麻烦,如下:

  • 使用 Map 接收 Post 请求参数:

    @PostMapping("/delete")
    public AjaxResult delete(@RequestBody Map<String,String> params){
        Long id = params.get("id");
        if (id == null) {
            throw AppException("参数错误");
        }
        service.deleteById(id);
        return AjaxResult.success();
    }
  • 使用 String 接收 Post 请求参数

    @PostMapping("/delete")
    public AjaxResult delete(@RequestBody String params){
        JSONObject paramsJSONObject = JSONObject.parseObject(params);
        Long id = paramsJSONObject.getLong("id");
        if (id == null) {
            throw AppException("参数错误");
        }
        service.deleteById(id);
        return AjaxResult.success();
    }
  • 从 Request 中获取参数

    @PostMapping("/delete")
    public AjaxResult delete(HttpServletRequest request) {
        String body = getRequestBody(request);
        JSONObject paramsJSONObject = JSONObject.parseObjec(body);
        Long id = paramsJSONObject.getLong("id");
        if (id == null) {
            throw AppException("参数错误");
        }
        service.deleteById(id);
        return AjaxResult.success();
    }
    
    /**
     * 从 request 中获取 body
     */
    private String getRequestBody(HttpServletRequest servletRequest) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            BufferedReader reader = servletRequest.getReader();
            char[] buf = new char[1024];
            int length;
            while ((length = reader.read(buf)) != -1) {
                stringBuilder.append(buf, 0, length);
            }
        } catch (IOException e) {
            log.error("读取流异常", e);
            throw new AppException(SystemError.PARSE_PARAMS_FAIL);
        }
        return stringBuilder.toString();
    }
  • 从 对象中获取

    @PostMapping("/delete")
    public ApiResponse delete(@RequestBody IdBean idBean) {
        if (idBean == null || idBean.getId() == null) {
            throw AppException("参数错误");
        }
        service.deleteById(id);
        return ApiResponse.createBySuccess();
    }
    @Data
    public class IdBean {
        private Long id;
    }

可以很明显的看到,虽然功能满足了,但是,不觉得有点怪吗,所以我们可以模仿 @RequestParam 自定义一个注解 @CustomParam,实现和 @RequestParam 同样的功能,只不过 @RequestParam 注解是从请求路径上获取参数,而我们自定义的 @CustomParam 注解则是从 request body 中获取参数

实现

1.定义注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomParam {
    @AliasFor("name")
    String value() default "";
    
    //要绑定到的请求参数的名称
    @AliasFor("value")
    String name() default "";

    //是否是必须的
    boolean required() default true;

    //当请求参数值未提供或为空时用作回退的默认值。提供默认值会隐式设置
    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

2.编写参数解析器

@Slf4j
public class CustomMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private static final String POST = "post";
    private static final String APPLICATION_JSON = "application/json";

    /**
     * 判断是否需要处理该参数
     *
     * @param parameter the method parameter to check
     * @return {@code true} if this resolver supports the supplied parameter;
     * {@code false} otherwise
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 只处理带有@CustomParam注解的参数
        return parameter.hasParameterAnnotation(CustomParam.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        String contentType = Objects.requireNonNull(servletRequest).getContentType();

        if (contentType == null || !contentType.contains(APPLICATION_JSON)) {
            log.error("解析参数异常,contentType需为{}", APPLICATION_JSON);
            throw new RuntimeException("解析参数异常,contentType需为application/json");
        }

        if (!POST.equalsIgnoreCase(servletRequest.getMethod())) {
            log.error("解析参数异常,请求类型必须为post");
            throw new RuntimeException("解析参数异常,请求类型必须为post");
        }
        return bindRequestParams(parameter, servletRequest);
    }

    private Object bindRequestParams(MethodParameter parameter, HttpServletRequest servletRequest) {
        CustomParam customParam = parameter.getParameterAnnotation(CustomParam.class);

        Class<?> parameterType = parameter.getParameterType();
        String requestBody = getRequestBody(servletRequest);
        Map paramObj = JSONObject.parseObject(requestBody, Map.class);

		String name = Strings.isBlank(customParam.value()) ? parameter.getParameterName() : customParam.value();
        Object value = paramObj.get(name);

        if (parameterType.equals(String.class)) {
            if (StringUtils.isBlank((String) value)) {
                log.error("参数解析异常,String类型参数不能为空");
                throw new RuntimeException("参数解析异常,String类型参数不能为空");
            }
        }

        if (customParam.required()) {
            if (value == null) {
                log.error("参数解析异常,require=true,值不能为空");
                throw new RuntimeException("参数解析异常,require=true,值不能为空");
            }
        } else {
            if (customParam.defaultValue().equals(ValueConstants.DEFAULT_NONE)) {
                log.error("参数解析异常,require=false,必须指定默认值");
                throw new RuntimeException("参数解析异常,require=false,必须指定默认值");
            }
            if (value == null) {
                value = customParam.defaultValue();
            }
        }

        return ConvertUtils.convert(value, parameterType);
    }

    /**
     * 获取请求body
     *
     * @param servletRequest request
     * @return 请求body
     */
    private String getRequestBody(HttpServletRequest servletRequest) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            BufferedReader reader = servletRequest.getReader();
            char[] buf = new char[1024];
            int length;
            while ((length = reader.read(buf)) != -1) {
                stringBuilder.append(buf, 0, length);
            }
        } catch (IOException e) {
            log.error("读取流异常", e);
            throw new RuntimeException("读取流异常");
        }
        return stringBuilder.toString();
    }
}

3.注册参数解析器

@Configuration
public class CustomParamResolverConfigurer implements WebMvcConfigurer {

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

4.完成

这样我们的功能就实现了,使用方法:

@PostMapping("/delete")
public AjaxResult delete(@CustomParam Long id) {
    service.deleteById(id);
    return AjaxResult.success();
}

写在最后,可能需要引入的包:

<!-- 阿里JSON解析器 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>${fastjson.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils-core -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils-core</artifactId>
    <version>1.8.3</version>
</dependency>

参考

文章1:https://juejin.cn/post/6844904122433404942

文章2:https://blog.csdn.net/qq_38225558/article/details/112572928