Java 如何从ConstraintViolationException获取查询参数名称
我有一个服务方法:Java 如何从ConstraintViolationException获取查询参数名称,java,spring-boot,hibernate-validator,javax.validation,Java,Spring Boot,Hibernate Validator,Javax.validation,我有一个服务方法: @GetMapping(path = "/api/some/path", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<?> getWhatever(@RequestParam(value = "page-number", defaultValue = "0") @Min(0) Integer pageNumber, ... @GetMapping(path=“/api/s
@GetMapping(path = "/api/some/path", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getWhatever(@RequestParam(value = "page-number", defaultValue = "0") @Min(0) Integer pageNumber, ...
@GetMapping(path=“/api/some/path”,products=MediaType.APPLICATION\u JSON\u VALUE)
public ResponseEntity getWhather(@RequestParam(value=“page number”,defaultValue=“0”)@Min(0)整数页码。。。
如果API的调用方没有为页码
查询参数提交正确的值,则会引发javax.ConstraintViolationexception
。异常消息的内容如下:
getWhatever.pageNumber必须等于或大于0
在响应正文中,我希望有以下消息:
页码必须等于或大于0
我希望我的消息具有查询参数的名称,而不是参数的名称。IMHO(包括参数的名称)将公开实现细节
问题是,我找不到带有查询参数名称的对象。似乎ConstraintViolationException
没有它
我正在spring boot中运行我的应用程序
任何帮助都将不胜感激
注:我去过其他声称能解决问题的类似线程,但实际上没有一个能解决问题。像这样在
@Min
注释中添加自定义消息
@Min(value=0, message="page-number must be equal or greater than {value}")
现在,您不能这样做(好吧,除非您为每个注释定义一个自定义消息,但我想这不是您想要的) 有趣的是,最近有人做了一件非常相似的事情: 这项工作已经合并到主分支,但我还没有发布包含这项工作的新的6.1alpha。这只是几天的问题 也就是说,我们已经考虑到了属性,现在您提出了这个问题,我们可能应该将其推广到更多的事情,包括方法参数 既然我们已经有了大致的想法,我认为概括起来应该不需要太多的工作
我将与贡献者和团队的其他成员讨论这一点,然后再与您联系。我认为获取查询参数的名称是不可能的,但如果有人知道方法,我希望被证明是错误的 正如Dmitry Bogdanovich所说,拥有自定义消息是我知道如何做一些接近您需要的事情的最简单也是唯一的方法。如果您说不想用这些消息混乱代码,您可以这样做: 在资源文件夹中添加ValidationMessages.properties文件。在这里,您可以说:
page_number.min=page-number must be equal or greater than {value}
现在,您可以使用注释并编写:
@Min(value = 0, message = "{page_number.min}")
这样,您就有了一个单一的源,可以在需要时更改有关消息的任何内容。以下是我在spring boot 2.0.3中如何使其工作的: 我必须在spring boot中覆盖并禁用
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默认的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
的一个实例添加到发现者的链接列表中。请注意,添加顺序在这里并不重要
以下是源代码:
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;
}
}
ValidationException
stuff是我处理验证错误的自定义方法,简而言之,它会生成一个错误DTO,它将与所有验证错误消息一起序列化为JSON。是的,但我认为这正是他想要避免的。是的,这是一个可行的解决方案,但我有大约50个服务类,每个都有有很多限制。这会使代码库混乱,并可能引入错误。我想将其自动化。此外,如果有人更改参数的名称并忘记更新消息,则会出现断开连接。感谢您的输入!已经有了ParameterNameProvider
合同,可用于obta是@RequestParam
注释中的名称。但是消息中仍然有方法名称。请您给我一些指导或给我发送一个示例实现。消息中的方法名称很好,我可以通过检查字符串轻松删除它。有关实际合同和注册实现的方法,请参阅。在utshell,您可以使用反射从已验证方法的参数中获取@RequestParam
注释,并返回名称列表。您可能会返回默认行为(实际参数名称)如果注释不存在。请阅读其他答案的注释我确实阅读了这些注释,我只是简单地为您提供了一种简单的方法,让您可以将所有这些消息放在一个位置,如果您的RequestParameter发生更改,您可以更改参数名称。据我所知,目前没有一种方法可以按照您的方式执行,我认为这是可以做到的这是一个解决办法。我认为有办法,一旦我证明它有效,我会在这里发布我的解决方案。感谢Guillaume Smet和@Gunnar的帮助,为我指引了正确的方向。“queryParam”不适用于嵌套对象。我建议将其替换为regex.String queryParam=queryParamPath.replaceAll(“.\\”,”);
@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));
}