5

Kiedy mam następujący model z JSR-303 (ramowej walidacja) Objaśnienia:wiążące Wiosna Boot i obsługi błędów walidacji w kontrolerze REST

public enum Gender { 
    MALE, FEMALE 
} 

public class Profile { 
    private Gender gender; 

    @NotNull 
    private String name; 

    ... 
} 

i następujących danych JSON:

{ "gender":"INVALID_INPUT" } 

W mój kontroler REST, chcę obsłużyć zarówno błędy wiązania (nieprawidłową wartość wyliczenia dla właściwości gender), jak i błędy sprawdzania poprawności (name właściwość nie może być pusta).

Poniższa metoda kontroler nie działa:

@RequestMapping(method = RequestMethod.POST) 
public Profile insert(@Validated @RequestBody Profile profile, BindingResult result) { 
    ... 
} 

Daje com.fasterxml.jackson.databind.exc.InvalidFormatException błąd serializacji przed związaniem lub walidacja odbywa.

Po pewnym fiddling, wymyśliłem tego kodu niestandardowego, który robi to, co chcę:

@RequestMapping(method = RequestMethod.POST) 
public Profile insert(@RequestBody Map values) throws BindException { 

    Profile profile = new Profile(); 

    DataBinder binder = new DataBinder(profile); 
    binder.bind(new MutablePropertyValues(values)); 

    // validator is instance of LocalValidatorFactoryBean class 
    binder.setValidator(validator); 
    binder.validate(); 

    // throws BindException if there are binding/validation 
    // errors, exception is handled using @ControllerAdvice. 
    binder.close(); 

    // No binding/validation errors, profile is populated 
    // with request values. 

    ... 
} 

Zasadniczo co ten kod robi, jest serializacji do rodzajowego mapie zamiast modelu, a następnie użyć niestandardowego kodu do związania modelować i sprawdzać błędy.

Mam następujące pytania:

  1. Czy niestandardowy kod do zrobienia tu lub tam jest bardziej standardowy sposób w ten sposób w Spring Boot?
  2. Jak działa adnotacja ? Jak mogę utworzyć własną adnotację, która działa tak, jak @Validated, aby hermetyzować mój niestandardowy kod wiążący?

Odpowiedz

1

Zwykle, gdy Spring MVC nie odczyta komunikatów http (np. Treść żądania), rzuci wystąpienie wyjątku HttpMessageNotReadableException. Tak więc, jeśli wiosna nie może połączyć się z twoim modelem, powinien wyrzucić ten wyjątek. Ponadto, jeśli wykonasz NOT zdefiniować BindingResult po każdym modelu, który ma zostać sprawdzony w swoich parametrach metody, w przypadku błędu sprawdzania poprawności, sprężyna wygeneruje wyjątek MethodArgumentNotValidException. Dzięki temu możesz utworzyć ControllerAdvice, która przechwytuje te dwa wyjątki i obsługuje je w pożądany sposób.

@ControllerAdvice(annotations = {RestController.class}) 
public class UncaughtExceptionsControllerAdvice { 
    @ExceptionHandler({MethodArgumentNotValidException.class, HttpMessageNotReadableException.class}) 
    public ResponseEntity handleBindingErrors(Exception ex) { 
     // do whatever you want with the exceptions 
    } 
} 
+1

wadą jest to, że nie dostaniesz BindingResult gdy wystąpi błąd wiążące. To znaczy. możesz zrobić 'ex.getBindingResult()' w wyjątku 'MethodArgumentNotValidException', ale nie w wyjątku' HttpMessageNotReadableException'. –

+0

Ten ostatni wydaje się rozsądny, ponieważ gdy wiązanie się nie powiedzie, nie moglibyśmy uzyskać wiążącego wyniku. Nie ma wiązania. –

+0

W moim widoku błędy wiązania, takie jak wstawianie ciągu znaków w polu int lub błędna wartość wyliczenia, powinny być traktowane jako błędy sprawdzania poprawności. Korzystanie z autonomicznych "bindów danych" również wiąże pola w "BindingResult", dzięki czemu usługa może zwrócić bardziej szczegółową odpowiedź błędu. –

3

Jest to kod, co mam używane w jednym z mojego projektu walidacji REST API w bagażniku sprężyny, to nie jest taka sama, jak zażądał, ale jest identyczna .. sprawdzić, czy to pomaga

@RequestMapping(value = "/person/{id}",method = RequestMethod.PUT) 
@ResponseBody 
public Object updatePerson(@PathVariable Long id,@Valid Person p,BindingResult bindingResult){ 
    if (bindingResult.hasErrors()) { 
     List<FieldError> errors = bindingResult.getFieldErrors(); 
     List<String> message = new ArrayList<>(); 
     error.setCode(-2); 
     for (FieldError e : errors){ 
      message.add("@" + e.getField().toUpperCase() + ":" + e.getDefaultMessage()); 
     } 
     error.setMessage("Update Failed"); 
     error.setCause(message.toString()); 
     return error; 
    } 
    else 
    { 
     Person person = personRepository.findOne(id); 
     person = p; 
     personRepository.save(person); 
     success.setMessage("Updated Successfully"); 
     success.setCode(2); 
     return success; 
    } 

Success.java

public class Success { 
int code; 
String message; 

public int getCode() { 
    return code; 
} 

public void setCode(int code) { 
    this.code = code; 
} 

public String getMessage() { 
    return message; 
} 

public void setMessage(String message) { 
    this.message = message; 
} 
} 

błąd.java

public class Error { 
int code; 
String message; 
String cause; 

public int getCode() { 
    return code; 
} 

public void setCode(int code) { 
    this.code = code; 
} 

public String getMessage() { 
    return message; 
} 

public void setMessage(String message) { 
    this.message = message; 
} 

public String getCause() { 
    return cause; 
} 

public void setCause(String cause) { 
    this.cause = cause; 
} 

} 

Można też rzucić okiem tutaj: Spring REST Validation