Как получить имя параметра запроса из ConstraintViolationException - PullRequest
0 голосов
/ 05 июня 2019

У меня есть метод обслуживания:

 @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 его нет.

Я запускаю свое приложение в весенней загрузке.

Любая помощь будет принята.

PS: У меня естьБыл в других аналогичных темах, которые утверждают, что решить проблему, на самом деле ни один из них не делает в действительности.

Ответы [ 4 ]

1 голос
/ 06 июня 2019

Вот как я заставил это работать в весенней загрузке 2.0.3:

Мне пришлось переопределить и отключить ValidationAutoConfiguration в весенней загрузке:

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 для параметра обнаружения имен параметров:

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));
    }

ValidationException Материал - это мой собственный способ работыв двух словах, с ошибками проверки, в двух словах выдается ошибка DTO, которая будет сериализована в JSON со всеми сообщениями об ошибках проверки.

1 голос
/ 05 июня 2019

Добавить пользовательское сообщение к @Min аннотации, подобной этой

@Min(value=0, message="page-number must be equal or greater than {value}")
0 голосов
/ 06 июня 2019

Я не думаю, что получить имя параметра запроса возможно, но хотелось бы, чтобы его ошиблись, если кто-то знает способ.

Как говорит Дмитрий Богданович, персонализированное сообщение - это самый простой и единственный способ узнать, как сделать что-то близкое к тому, что вам нужно. Если вы говорите, что не хотите загромождать свой код этими сообщениями, вы можете просто сделать это:

Добавьте файл ValidationMessages.properties в папку ресурсов. Здесь можно просто сказать:

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

Теперь вы можете использовать аннотацию и написать:

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

Таким образом, у вас есть один источник, который может изменить что-либо в сообщении при необходимости.

0 голосов
/ 05 июня 2019

Прямо сейчас вы не можете сделать это (хорошо, за исключением того, что вы определяете пользовательское сообщение для каждой аннотации, но я полагаю, это не то, что вам нужно).

Как ни странно, кто-то недавно работал над чем-то очень похожим:https://github.com/hibernate/hibernate-validator/pull/1029.

Эта работа была объединена с основной веткой, но я еще не выпустил новую альфа-версию 6.1, содержащую эту работу.Это вопрос дней.

Как говорится, мы имели в виду свойства, и теперь, когда вы об этом спросите, мы, вероятно, должны обобщить это для большего количества вещей, включая параметры метода.

Теперь, когда мыЯ имею общее представление, что не стоит слишком много работать для его обобщения, я думаю.

Я обсудю это с участником и остальной частью команды и вернусь к вам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...