Spring Rest ErrorHandling @ControllerAdvice/@Valid
У меня возникают проблемы при объединении аннотаций @ControllerAdvice и @Valid вместе с контроллером REST.
У меня есть контроллер отдыха, объявленный следующим образом:
@Controller
public class RestExample {
...
/**
* <XmlRequestUser><username>user1</username><password>password</password><name>Name</name><surname>Surname</surname></XmlRequestUser>
* curl -d "@restAddRequest.xml" -H "Content-Type:text/xml" http://localhost:8080/SpringExamples/servlets/rest/add
*/
@RequestMapping(value="rest/add", method=RequestMethod.POST)
public @ResponseBody String add(@Valid @RequestBody XmlRequestUser xmlUser) {
User user = new User();
user.setUsername(xmlUser.getUsername());
user.setPassword(xmlUser.getPassword());
user.setName(xmlUser.getName());
user.setSurname(xmlUser.getSurname());
// add user to the database
StaticData.users.put(xmlUser.getUsername(), user);
LOG.info("added user " + xmlUser.getUsername());
return "added user " + user.getUsername();
}
}
И класс ErrorHandler:
@ControllerAdvice
public class RestErrorHandler extends ResponseEntityExceptionHandler {
private static Logger LOG = Logger.getLogger(RestErrorHandler.class);
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Object> handleException(final RuntimeException e, WebRequest request) {
LOG.error(e);
String bodyOfResponse = e.getMessage();
return handleExceptionInternal(e, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
Проблема заключается в том, что если я добавлю "throw new RuntimeException" в метод RestExample.add, то исключение корректно обрабатывается классом RestErrorHandler.
Однако, когда скручивание недопустимого запроса к контроллеру, RestErrorHandler не обнаруживает исключения, созданного валидатором, и я получаю ответ 400 BadRequest. (Для недопустимого запроса я имею в виду запрос xml, где имя пользователя не указано)
Обратите внимание, что класс XmlRequestUser автогенерируется плагином maven-jaxb2-plugin + krasa-jaxb-tools (pom.xml):
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaDirectory>src/main/xsd</schemaDirectory>
<schemaIncludes>
<include>*.xsd</include>
</schemaIncludes>
<args>
<arg>-XJsr303Annotations</arg>
<arg>-XJsr303Annotations:targetNamespace=http://www.foo.com/bar</arg>
</args>
<plugins>
<plugin>
<groupId>com.github.krasa</groupId>
<artifactId>krasa-jaxb-tools</artifactId>
<version>${krasa-jaxb-tools.version}</version>
</plugin>
</plugins>
</configuration>
</plugin>
Сгенерированный класс имеет правильные поля @NotNull Annotation on Username и Password.
Мой context.xml очень прост, содержащий только сканер для контроллеров и включающий mvc: annotation-driven
<context:component-scan base-package="com.aa.rest" />
<mvc:annotation-driven />
Кто-нибудь знает, как объединить рабочие @ControllerAdvice и @Valid аннотации вместе в контроллере REST?
Спасибо заранее.
Антонио
Ответы
Ответ 1
Вы на правильном пути, но вам нужно переопределить handleMethodArgumentNotValid() вместо метода handleException()
, например
@ControllerAdvice
public class RestErrorHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
LOG.error(exception);
String bodyOfResponse = exception.getMessage();
return new ResponseEntity(errorMessage, headers, status);
}
}
Из JavaDoc исключение MethodArgumentNotValidException:
Исключение, которое выдается, когда проверка аргумента, аннотированного @Valid, не выполняется.
Другими словами, MethodArgumentNotValidException
вызывается при сбое проверки. Он обрабатывается методом handleMethodArgumentNotValid()
предоставленным ResponseEntityExceptionHandler
, который необходимо переопределить, если вы хотите пользовательскую реализацию.
Ответ 2
В дополнение к этому у меня возникла еще одна проблема при рекурсивной проверке.
В сгенерированных классах отсутствовала аннотация @valid на других XmlElements.
Причина этого связана с тем, что я ошибочно использовал пространства имен:
Плагин настроен на использование определенного пространства имен
<arg>-XJsr303Annotations:targetNamespace=http://www.foo.com/bar</arg>
Таким образом, XSD должен иметь это как пространство имен мишеней (например,):
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.com/bar"
xmlns:foo="http://www.foo.com/bar" >
<xs:complexType name="XmlPassword">
<xs:sequence>
<xs:element name="password" type="xs:string" />
<xs:element name="encryption" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="XmlToken">
<xs:sequence>
<xs:element name="password" type="foo:XmlPassword" />
</xs:sequence>
</xs:complexType>
</xs:schema>
С уважением
Антонио
Ответ 3
Лучший формат ответного сообщения:
@ControllerAdvice
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
.map(f -> new FieldErrorVM(f.getObjectName(), f.getField(), f.getCode()))
.collect(Collectors.toList());
return handleExceptionInternal(ex, "Method argument not valid, fieldErrors:" + fieldErrors ,new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
}