DropWizard中的Bean验证与自定义Json输出

时间:2017-04-21 12:23:52

标签: java validation dropwizard

我开发了一个具有各种Rest功能的Web服务。我想使用标准@Valid注释来验证我的bean。但我想修改输出json错误。

验证的错误消息目前的格式如下:

{
  "errors": [
    "someString size must be between 0 and 140",
    "anotherString cannot contain numbers"
  ]
}

但我希望错误消息的格式如下:

{
    "errors": [{
            "someString": "size must be between 0 and 140"
        },
        {
            "anotherString": "cannot contain numbers"
        }
    ]
}

{
    "errors": [{
            "field": "someString"
            "error": "size must be between 0 and 140"
        },
        {
            "field": "anotherString"
            "error": "cannot contain numbers"
        }
    ]
}

我知道如何通过向验证注释提供message="some message about strings"来改变错误消息,甚至将ValidationMEssages.properties用作所有错误消息的公共位置。但是,如果出现错误,我无法改变输出格式。

我已阅读以下文档,但我需要更多指导。 http://www.dropwizard.io/1.0.0/docs/manual/validation.html

这是我的第一个DropWizard项目,我习惯于在Spring中开发。

提前致谢。

1 个答案:

答案 0 :(得分:2)

我为自己的问题找到了解决方案。如果有人应该像我一样遇到同样的问题,我决定发布它。

这适用于DropWizard 1.0,我还没有测试它是否适用于早期版本或更高版本,所以请记住这一点。我无法为您提供完整的解决方案,但我已发布 我的解决方案是代码片段,所以不要指望你可以简单地复制/粘贴和编译。

解决方案实际上很简单,你只需要为ConstraintViolationException实现自己的ExceptionMapper并使用DropWizard重新编译它。

通过提供模板或常规文本,您可以轻松地为bean验证指定自己的消息。即

@NotNull(message="God damn it, Morty. I ask you to do one thing!")

@NotNull(message="{morty.error}")

模板位于ValidationMessages.properties,您必须自己创建并放置在src/main/resources/

<强> ValidationMessages.properties

morty.error=God damn it, Morty. I ask you to do one thing!

无论如何,这是我的解决方案。

<强> SomeApplication.class

//Class where you setup DropWizard
public class SomeApplication extends Application<SomeConfiguration> {

    @Override
    public void run(SomeConfiguration conf, Environment environment) throws Exception {

        //Remove all default ExceptionMappers
        ((DefaultServerFactory)conf.getServerFactory()).setRegisterDefaultExceptionMappers(false);

        //Register your custom ExceptionMapper for ConstraintViolationException
        environment.jersey().register(new CustomExceptionMapper());

        //Restore the default ExceptionsMappers that you just removed
        environment.jersey().register(new LoggingExceptionMapper<Throwable>() {});
        environment.jersey().register(new JsonProcessingExceptionMapper());
        environment.jersey().register(new EarlyEofExceptionMapper());
    }
}

<强> CustomExceptionMapper.class

//This is where the magic happens. 
//This is your custom ExceptionMapper for ConstraintViolationException
@Provider
public class CustomExceptionMapper implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(ConstraintViolationException cve) {
        //List to store all the exceptions that you whish to output
        //ValidationErrors is a custom object, which you can see further down
        ValidationErrors validationErrors = new ValidationErrors();

        //Loop through all the ConstraintViolations
        for(ConstraintViolation<?> c : cve.getConstraintViolations()){

            //We retrieve the variable name or method name where the annotation was called from.
            //This will be your property name for your JSON output.
            String field = ((PathImpl)c.getPropertyPath()).getLeafNode().getName();

            //If field is null, then the notation is probably at a class level.
            //Set field to class name
            if(field == null){
                field = c.getLeafBean().getClass().getSimpleName();
            }

            //c.getMessage() is the error message for your annotation.
            validationErrors.add(field, c.getMessage());
        }
        //Return a response with proper http status.
        return Response.status(422).entity(validationErrors).build();
    }
}

<强> ValidationErrors

//There is not really any magic happening here.
//This class is just a wrapper for a List with the type ValidationObject.
public class ValidationErrors {

    public List<ValidationObject> errors = new ArrayList<ValidationObject>();

    public void add(String field, String error){
        errors.add(new ValidationObject(field, error));
    }
}

<强> ValidationObject.class

//Once again, no magic
public class ValidationObject {

    //This will be your property names
    private String field, error;

    public ValidationObject(String field, String error){
        this.field = field;
        this.error = error;
    }

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }
}

<强> TestClass.class

//This is just a class to showcase the functionality
//I have not posted any codesnippers for this @CustomClassConstaint, 
//it is a custom annotaiton.
//I only included this annotation to show how  
//the CustomExceptionMapper handles annotations on a class level
@CustomClassConstaint 
public class TestClass {

    @NotNull
    @Size(min = 2, max = 5)
    public String testString1;

    @NotNull
    @Size(min = 2, max = 5)
    public String testString2;

    @Min(10)
    @Max(20)
    public int testInt1;

    @Min(10)
    @Max(20)
    public int testInt2;

}

用于测试的休息功能

    //Some rest function to showcase
    @POST
    @Path("/path/to/test")
    //Remember @Valid or annotations will not be validated
    public Response callRestTestMethod(@Valid TestClass testObject){
        return Response.ok().build();
    }

测试输入:

POST /path/to/test
{
  "testString1": null,
  "testString2": "",
  "testInt1": 9,
  "testInt2": 21
}

测试输出:

订单有点随机,每次调用callRestTestMethod(...)时都会更改。验证是逐个触发的,因为它们在组件树中,我不知道是否可以控制订单。

{
  "errors": [
    {
      "field": "TestClass",
      "error": "custom error msg"
    },
    {
      "field": "testInt1",
      "error": "must be greater than or equal to 10"
    },
    {
      "field": "testString1",
      "error": "may not be null"
    },
    {
      "field": "testInt2",
      "error": "must be less than or equal to 20"
    },
    {
      "field": "testString2",
      "error": "size must be between 2 and 5"
    }
  ]
}