7

假设我有一个简单的 POJO,如下所示,带有 Jackson 2.1 和 Hibernate Validator 4.3.1 注释:

final public class Person {
  @JsonProperty("nm")
  @NotNull
  final public String name;

  public Person(String name) {
      this.name = name;
  }
}

我将这样的 JSON 发送到 Web 服务:

{"name": null}

当它报告 ConstraintViolation 时,休眠使用类成员标识符“名称”而不是 JsonProperty 注释值。有谁知道是否可以让 Hibernate Validator 查看类的注释并改用该值?

4

3 回答 3

8

不幸的是,没有简单的方法可以做到这一点。但这里有一些可以帮助你的见解:

解析约束违规

从 中ConstraintViolationException,您可以获得一组ConstraintViolation,它公开了约束违规上下文:

从属性路径中,您可以获取叶节点:

Path propertyPath = constraintViolation.getPropertyPath();
Optional<Path.Node> leafNodeOptional = 
        StreamSupport.stream(propertyPath.spliterator(), false).reduce((a, b) -> b);

然后检查节点的类型是否为PROPERTY并获取其名称:

String nodeName = null;

if (leafNodeOptional.isPresent()) {
    Path.Node leafNode = leafNodeOptional.get();
    if (ElementKind.PROPERTY == leafNode.getKind()) {
        nodeName = leafNode.getName();
    }
}

与杰克逊一起自省一堂课

要从叶 bean 类中获取可用的 JSON 属性,您可以使用 Jackson 对其进行自省(有关详细信息,请参阅此答案和此答案):

Class<?> beanClass = constraintViolation.getLeafBean().getClass();
JavaType javaType = mapper.getTypeFactory().constructType(beanClass);

BeanDescription introspection = mapper.getSerializationConfig().introspect(javaType);
List<BeanPropertyDefinition> properties = introspection.findProperties();

然后通过将叶节点名称与以下Field名称进行比较来过滤属性BeanPropertyDefinition

Optional<String> jsonProperty = properties.stream()
        .filter(property -> nodeName.equals(property.getField().getName()))
        .map(BeanPropertyDefinition::getName)
        .findFirst();

使用 JAX-RS?

使用 JAX-RS(如果您正在使用它),您可以定义一个ExceptionMapper来处理ConstraintViolationExceptions:

@Provider
public class ConstraintViolationExceptionMapper 
                 implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(ConstraintViolationException exception) {
        ...
    }
}

ObjectMapper在您的 中使用ExceptionMapper,您可以ContextResolver<T>为它提供一个:

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public ObjectMapperContextResolver() {
        mapper = createObjectMapper();
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return mapper;
    }

    private ObjectMapper createObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
}

Providers将接口注入您的ExceptionMapper

@Context
private Providers providers;

查找您的ContextResolver<T>然后获取ObjectMapper实例:

ContextResolver<ObjectMapper> resolver = 
        providers.getContextResolver(ObjectMapper.class, MediaType.WILDCARD_TYPE);
ObjectMapper mapper = resolver.getContext(ObjectMapper.class);

如果您有兴趣获取@XxxParam名称,请参阅此答案

于 2017-05-31T10:07:30.160 回答
3

不,那是不可能的。Hibernate Validator 5 (Bean Validation 1.1) 具有ParameterNameProviders 的概念,它返回要报告的名称,以防违反方法参数约束但没有可比的属性约束。

于 2013-09-18T21:41:08.200 回答
0

我提出了这个问题,因为我正在使用question-spring-web模块进行验证,并且它不支持开箱即用的 bean 定义名称作为休眠。所以我想出了以下逻辑来覆盖createViolationofConstraintViolationAdviceTrait并获取该字段的JSONProperty字段名称并再次创建违规行为。

public class CustomBeanValidationAdviceTrait implements ValidationAdviceTrait {

    private final ObjectMapper objectMapper;

    public CustomBeanValidationAdviceTrait(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }


 @Override
    public Violation createViolation(ConstraintViolation violation) {
        String propertyName = getPropertyName(violation.getRootBeanClass(), violation.getPropertyPath().toString());
        return new Violation(this.formatFieldName(propertyName), violation.getMessage());
    }


 private String getPropertyName(Class clazz, String defaultName) {
        JavaType type = objectMapper.constructType(clazz);
        BeanDescription desc = objectMapper.getSerializationConfig().introspect(type);
        return desc.findProperties()
                .stream()
                .filter(prop -> prop.getInternalName().equals(defaultName))
                .map(BeanPropertyDefinition::getName)
                .findFirst()
                .orElse(defaultName);
    }
于 2021-03-29T22:24:08.387 回答