6

我有一个服务方法:

 @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则会引发。异常消息的内容如下:

getWhatever.pageNumber must be equal or greater than 0

在响应正文中,我希望收到此消息:

page-number must be equal or greater than 0

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

问题是,我找不到带有查询参数名称的对象。好像ConstraintViolationException没有啊

我在 spring-boot 中运行我的应用程序。

任何帮助,将不胜感激。

PS:我去过其他声称可以解决问题的类似线程,实际上它们都没有。

4

4 回答 4

3

这是我在 spring-boot 2.0.3 中如何使它工作的:

我不得不ValidationAutoConfiguration在 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位于同一个包中,它几乎是DefaultParameterNameDiscovererspring-boot 的参数名称发现器的默认实现的复制粘贴:

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到发现者的链接列表中。请注意,添加的顺序在这里很重要。

这是源代码:

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属性并将其作为参数名称返回。

接下来的事情是禁用自动验证配置,不知何故,没有它就无法工作。这个注释虽然可以解决问题: @SpringBootApplication(exclude = {ValidationAutoConfiguration.class})

此外,您需要为您的自定义处理程序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));
    }

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

于 2019-06-06T18:29:24.203 回答
1

@Min像这样向注释添加自定义消息

@Min(value=0, message="page-number must be equal or greater than {value}")
于 2019-06-05T20:57:58.933 回答
1

现在,你不能这样做(好吧,除非你为每个注释定义一个自定义消息,但我想这不是你想要的)。

有趣的是,最近有人在做一些非常相似的事情:https ://github.com/hibernate/hibernate-validator/pull/1029 。

这项工作已合并到 master 分支,但我尚未发布包含此工作的新 6.1 alpha。这是几天的事情。

话虽如此,我们已经考虑了属性,现在您提出这个问题,我们可能应该将其推广到更多的东西,包括方法参数。

既然我们有了大致的想法,我认为概括它不应该做太多的工作。

我将与贡献者和团队的其他成员讨论这个问题,然后回复你。

于 2019-06-05T20:59:50.993 回答
1

我认为获取查询参数的名称是不可能的,但如果有人知道方法,我希望被证明是错误的。

正如 Dmitry Bogdanovich 所说,拥有自定义消息是我知道如何做一些接近你需要的事情的最简单也是唯一的方法。如果你说你不想让这些消息使你的代码混乱,你可以这样做:

在资源文件夹中添加 ValidationMessages.properties 文件。在这里你可以说:

page_number.min=page-number must be equal or greater than {value}

现在您可以使用注释并编写:

@Min(value = 0, message = "{page_number.min}")

这样,您就可以在需要时通过单一来源更改有关消息的任何内容。

于 2019-06-06T10:08:02.390 回答