问题
given the following dto and controller
public class PasswordCredentials implements AuthenticationProvider {
@NotNull
@NotEmpty
@JsonProperty( access = JsonProperty.Access.WRITE_ONLY )
private String user;
@NotNull
@NotEmpty
@JsonProperty( access = JsonProperty.Access.WRITE_ONLY )
private CharSequence pass;
public void setPass( final CharSequence pass ) {
this.pass = pass;
}
public void setUser( final String user ) {
this.user = user;
}
@Override
public Authentication toAuthentication() {
return new UsernamePasswordAuthenticationToken( user, pass );
}
}
@RestController
@RequestMapping( path = "authentication" )
class AuthenticationController {
private final AuthenticationManager am;
AuthenticationController( final AuthenticationManager am ) {
this.am = am;
}
@RequestMapping( path = "password", method = RequestMethod.POST, consumes = {
"!" + MediaType.APPLICATION_FORM_URLENCODED_VALUE
} )
ResponseEntity<?> login( @Valid @RequestBody final PasswordCredentials credentials ) {
Authentication authenticate = am.authenticate( credentials.toAuthentication() );
if ( authenticate.isAuthenticated() ) {
return ResponseEntity.status( HttpStatus.NO_CONTENT ).build();
}
return ResponseEntity.status( HttpStatus.FORBIDDEN ).build();
}
}
if for example pass
is null there will be a validation error, and a 400 will happen without ever calling my controller, which is fine. That 400 however has no content, is there any way to have the controllers BindResults
output as content so that the consumer of the API knows what caused the problem? Ideally I would not do this in the controller method, so that it would happen on all controllers?
I was able to get this behavior with spring data rest as follows, but I'd like it for all API controllers.
class RestConfig extends RepositoryRestConfigurerAdapter {
@Bean
Validator validator() {
return new LocalValidatorFactoryBean();
}
@Override
public void configureValidatingRepositoryEventListener(
final ValidatingRepositoryEventListener validatingListener ) {
Validator validator = validator();
//bean validation always before save and create
validatingListener.addValidator( "beforeCreate", validator );
validatingListener.addValidator( "beforeSave", validator );
}
@Override
public void configureRepositoryRestConfiguration( final RepositoryRestConfiguration config ) {
config.setBasePath( "/v0" );
config.setReturnBodyOnCreate( false );
config.setReturnBodyOnUpdate( false );
}
回答1:
Spring have @ControllerAdvice
and @ExceptionHandler
annotation to handle errors in controllers.
@ControllerAdvice
public class ExceptionTranslator {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public Error processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
.....
return new Error();
}
// Other exceptions
}
回答2:
i want to improve the answer of Anton Novopashin: just return the error in response entity.
@ControllerAdvice
public class ExceptionTranslator {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResponseEntity<String> processValidationError(MethodArgumentNotValidException ex) {
return new ResponseEntity(ex.getMessage, HttpStatus.BAD_REQUEST);
}
// Other exceptions
}
回答3:
I'm not sure who or why downvoted the existing answers but they are both right - the best way to handle validation errors would be to declare a @ControllerAdvice
and then handle the exceptions there. Here's a snippet of my global error handler, taken from an existing project:
@ControllerAdvice
@ResponseBody
public class RestfulErrorHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResponse methodValidationError(MethodArgumentNotValidException e) {
final ErrorResponse response = new ErrorResponse();
for (ObjectError error : e.getBindingResult().getAllErrors()) {
if (error instanceof FieldError) {
response.addFieldError((FieldError) error);
} else {
response.addGeneralError(error.getDefaultMessage());
}
}
return response;
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public ErrorResponse constraintViolationError(ConstraintViolationException e) {
final ErrorResponse response = new ErrorResponse();
for (ConstraintViolation<?> v : e.getConstraintViolations()) {
response.addFieldError(v.getPropertyPath(), v.getMessage());
}
return response;
}
}
You should also process ConstraintViolationException
s since they too may be thrown. Here's a link to my ErrorResponse class, I'm including it as a Gist so as not to obscure the main point.
You should also probably process the RepositoryConstraintViolationException, I'm not sure if spring-data-rest
handles them already.
来源:https://stackoverflow.com/questions/39166509/how-can-i-have-validation-errors-printed-on-failure