如何从ConstraintViolationException获取查询参数名称 [英] How to get query parameter name from ConstraintViolationException

查看:192
本文介绍了如何从ConstraintViolationException获取查询参数名称的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一种服务方法:

 @GetMapping(path = "/api/some/path", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getWhatever(@RequestParam(value = "page-number", defaultValue = "0") @Min(0) Integer pageNumber, ...

如果API的调用者没有为page-number查询参数提交正确的值,则会引发javax.ConstraintViolationexception.异常消息将如下所示:

If the caller of an API doesn't submit a proper value for page-number query parameter, javax.ConstraintViolationexception is being raised. The message of the exception would read smth like:

getWhatever.pageNumber must be equal or greater than 0

在响应正文中,我想显示以下消息:

In the response body, I would like to have this message instead:

page-number must be equal or greater than 0

我希望我的消息具有查询参数的名称,而不是参数的名称.恕我直言,包括参数名称在内的都是公开实现细节.

I want my message to have the name of a query parameter, not the name of the argument. IMHO, including the name of the argument is exposing the implementation details.

问题是,我找不到带有查询参数名称的对象.似乎ConstraintViolationException没有它.

The problem is, I cannot find an object that is carrying query parameter name. Seems like the ConstraintViolationException doesn't have it.

我正在春季引导中运行我的应用.

I am running my app in spring-boot.

任何帮助将不胜感激.

P.S .:我去过其他声称可以解决问题的类似话题,但实际上它们都没有.

P.S.: I have been to the other similar threads that claim to solve the problem, none of them actually do in reality.

推荐答案

这是我在Spring-boot 2.0.3中使其工作的方式:

Here is how I made it work in spring-boot 2.0.3:

我必须在spring-boot中覆盖并禁用ValidationAutoConfiguration:

I had to override and disable ValidationAutoConfiguration in spring-boot:

import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

import javax.validation.Validator;

@Configuration
public class ValidationConfiguration {
    public ValidationConfiguration() {
    }

    @Bean
    public static LocalValidatorFactoryBean validator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setParameterNameDiscoverer(new CustomParamNamesDiscoverer());
        MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
        factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
        return factoryBean;
    }

    @Bean
    public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator) {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        boolean proxyTargetClass = (Boolean) environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
        processor.setProxyTargetClass(proxyTargetClass);
        processor.setValidator(validator);
        return processor;
    }
}

CustomParamNamesDiscoverer位于同一程序包中,它几乎是DefaultParameterNameDiscoverer的复制粘贴,它是spring-boot的param名称发现器的默认实现:

CustomParamNamesDiscoverer sits in the same package and it is a pretty much a copy-paste of DefaultParameterNameDiscoverer, spring-boot's default implementation of param name discoverer:

import org.springframework.core.*;
import org.springframework.util.ClassUtils;

public class CustomParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
    private static final boolean kotlinPresent = ClassUtils.isPresent("kotlin.Unit", CustomParameterNameDiscoverer.class.getClassLoader());

    public CustomParameterNameDiscoverer() {
        if (kotlinPresent) {
            this.addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
        }

        this.addDiscoverer(new ReqParamNamesDiscoverer());
        this.addDiscoverer(new StandardReflectionParameterNameDiscoverer());
        this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
    }
}

我希望它保持完整无缺(您甚至可以在其中看到kotlin支票),唯一的补充是: 我正在将ReqParamNamesDiscoverer的实例添加到发现者的链接列表中.请注意,添加顺序在这里很重要.

I wanted it to remain pretty much intact (you can see even kotlin checks in there) with the only addition: I am adding an instance of ReqParamNamesDiscoverer to the linked lists of discoverers. Note that the order of addition does matter here.

这是源代码:

import com.google.common.base.Strings;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RequestParam;

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ReqParamNamesDiscoverer implements ParameterNameDiscoverer {

    public ReqParamNamesDiscoverer() {
    }

    @Override
    @Nullable
    public String[] getParameterNames(Method method) {
        return doGetParameterNames(method);
    }

    @Override
    @Nullable
    public String[] getParameterNames(Constructor<?> constructor) {
        return doGetParameterNames(constructor);
    }

    @Nullable
    private static String[] doGetParameterNames(Executable executable) {
        Parameter[] parameters = executable.getParameters();
        String[] parameterNames = new String[parameters.length];
        for (int i = 0; i < parameters.length; ++i) {
            Parameter param = parameters[i];
            if (!param.isNamePresent()) {
                return null;
            }
            String paramName = param.getName();
            if (param.isAnnotationPresent(RequestParam.class)) {
                RequestParam requestParamAnnotation = param.getAnnotation(RequestParam.class);
                if (!Strings.isNullOrEmpty(requestParamAnnotation.value())) {
                    paramName = requestParamAnnotation.value();
                }
            }
            parameterNames[i] = paramName;
        }
        return parameterNames;
    }
}

如果用RequestParam注释对参数进行注释,则我正在检索value属性并将其作为参数名称返回.

If parameter is annotated with RequestParam annotation, I am retrieving the value attribute and return it as a parameter name.

接下来的事情是禁用自动验证配置,以某种方式,没有它就无法工作.这个注解可以解决问题: @SpringBootApplication(exclude = {ValidationAutoConfiguration.class})

The next thing was disabling auto validation config, somehow, it doesn't work without it. This annotation does the trick though: @SpringBootApplication(exclude = {ValidationAutoConfiguration.class})

此外,您还需要为ConstraintValidationException提供一个自定义处理程序:

Also, you need to have a custom handler for your ConstraintValidationException :

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public ErrorDTO handleConstraintViolationException(ConstraintViolationException ex) {
        Map<String, Collection<String>> errors = new LinkedHashMap<>();
        ex.getConstraintViolations().forEach(constraintViolation -> {
            String queryParamPath = constraintViolation.getPropertyPath().toString();
            log.debug("queryParamPath = {}", queryParamPath);
            String queryParam = queryParamPath.contains(".") ?
                    queryParamPath.substring(queryParamPath.indexOf(".") + 1) :
                    queryParamPath;
            String errorMessage = constraintViolation.getMessage();
            Collection<String> perQueryParamErrors = errors.getOrDefault(queryParam, new ArrayList<>());
            perQueryParamErrors.add(errorMessage);
            errors.put(queryParam, perQueryParamErrors);
        });
        return validationException(new ValidationException("queryParameter", errors));
    }

ValidationException东西是我处理验证错误的自定义方式,简而言之,它会产生错误DTO,并将其与所有验证错误消息一起序列化为JSON.

ValidationException stuff is my custom way of dealing with validation errors, in a nutshell, it produces an error DTO, which will be serialized into JSON with all the validation error messages.

这篇关于如何从ConstraintViolationException获取查询参数名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆