rest api
我对此感到有些ham愧,但是直到昨天,我才知道我可以通过使用@Valid和@RequestBody批注将验证添加到REST API中。 这在Spring MVC 3.0中不起作用,由于某种原因,我没有注意到在Spring MVC 3.1中添加了对此功能的支持 。 我从不喜欢旧的方法,因为我不得不
- 将Validator和MessageSource bean注入我的控制器,以便我可以验证请求并在验证失败时获取本地化的错误消息。
- 在每个必须验证输入的控制器方法中调用验证方法。
- 将验证逻辑移到由控制器类扩展的公共基类中。
当我发现不必再做这些事情时,我决定写这篇博客文章,并与大家分享我的发现。
注意:如果我们想在Spring Framework中使用JSR-303支持的验证,则必须向我们的类路径添加JSR-303提供程序。 本博客文章的示例应用程序使用Hibernate Validator 4.2.0,它是Bean验证API(JSR-303)的参考实现。
让我们首先看一下本博客文章中使用的DTO类。 CommentDTO类的源代码如下所示:
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;public class CommentDTO {@NotEmpty@Length(max = 140)private String text;//Methods are omitted.
}
让我们继续前进,了解如何使用Spring MVC 3.1将验证添加到REST API。
Spring MVC 3.1是一个好的开始
我们可以按照以下步骤将验证添加到REST API:
- 实施控制器方法,并确保其输入经过验证。
- 实现处理验证错误的逻辑。
以下小节将介绍这两个步骤。
实施控制器
我们可以通过执行以下步骤来实现我们的控制器:
- 创建一个名为CommentController的类,并使用@Controller注释对该类进行注释。
- 将一个add()方法添加到CommentController类,该方法将添加的注释作为方法参数。
- 使用@RequestMapping和@ResponseBody注释对方法进行注释。
- 将@Valid和@RequestBody批注应用到方法参数。
- 返回添加的评论。
CommentController类的源代码如下所示:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;@Controller
public class CommentController {@RequestMapping(value = "/api/comment", method = RequestMethod.POST)@ResponseBodypublic CommentDTO add(@Valid @RequestBody CommentDTO comment) {return comment;}
}
现在,我们向控制器添加了新方法,并对其进行了验证。 验证失败时,将引发MethodArgumentNotValidException 。 让我们找出验证失败时如何向API用户返回有意义的响应。
处理验证错误
通过执行以下步骤,我们可以实现处理验证错误的逻辑:
- 实现数据传输对象,其中包含返回给我们REST API用户的信息。
- 实现异常处理程序方法。
下面将更详细地描述这些步骤。
创建数据传输对象
首先,我们必须创建数据传输对象,其中包含返回给REST API用户的信息。 我们可以按照以下步骤进行操作:
- 创建一个DTO,其中包含单个验证错误的信息。
- 创建一个DTO,将这些验证错误包装在一起。
让我们开始吧。
第一个DTO的源代码如下所示:
public class FieldErrorDTO {private String field;private String message;public FieldErrorDTO(String field, String message) {this.field = field;this.message = message;}//Getters are omitted.
}
第二个DTO的实现非常简单。 它包含一个FieldErrorDTO对象列表和一个用于向列表中添加新字段错误的方法。 ValidationErrorDTO的源代码如下所示:
import java.util.ArrayList;
import java.util.List;public class ValidationErrorDTO {private List<FieldErrorDTO> fieldErrors = new ArrayList<>();public ValidationErrorDTO() {}public void addFieldError(String path, String message) {FieldErrorDTO error = new FieldErrorDTO(path, message);fieldErrors.add(error);}//Getter is omitted.
}
以下清单提供了一个示例Json文档,当验证失败时,该文档将发回给我们的API用户:
{"fieldErrors":[{"field":"text","message":"error message"}]
}
让我们看看如何实现异常处理程序方法,该方法创建一个新的ValidationErrorDTO对象并返回创建的对象。
实现异常处理程序方法
我们可以按照以下步骤将异常处理程序方法添加到我们的控制器中:
- 将一个MessageSource字段添加到CommentController类。 消息源用于获取验证错误的本地化错误消息。
- 通过使用构造函数注入来注入MessageSource bean。
- 将一个processValidationError()方法添加到CommentController类。 此方法返回ValidationErrorDTO对象,并将MethodArgumentNotValidException对象作为方法参数。
- 使用@ExceptionHandler注释对方法进行注释,并确保在引发MethodArgumentNotValidException时调用该方法。
- 使用@ResponseStatus注释对方法进行注释,并确保返回HTTP状态代码400(错误请求)。
- 使用@ResponseBody注释对方法进行注释。
- 实现该方法。
让我们仔细看看processValidationError()方法的实现。 我们可以通过执行以下步骤来实现此方法:
- 获取FieldError对象的列表并进行处理。
- 一次处理一场错误。
- 尝试使用MessageSource对象,当前语言环境和已处理字段错误的错误代码来解决本地化的错误消息。
- 返回已解决的错误消息。 如果从属性文件中找不到错误消息,请返回最准确的字段错误代码。
- 通过调用ValidationErrorDTO类的addFieldError()方法来添加新的字段错误。 将字段名称和已解决的错误消息作为方法参数传递。
- 处理完每个字段错误后,返回创建的ValidationErrorDTO对象。
CommentController类的源代码如下所示:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;
import java.util.List;
import java.util.Locale;@Controller
public class CommentController {private MessageSource messageSource;@Autowiredpublic CommentController(MessageSource messageSource) {this.messageSource = messageSource;}//The add() method is omitted.@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {BindingResult result = ex.getBindingResult();List<FieldError> fieldErrors = result.getFieldErrors();return processFieldErrors(fieldErrors);}private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {ValidationErrorDTO dto = new ValidationErrorDTO();for (FieldError fieldError: fieldErrors) {String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);dto.addFieldError(fieldError.getField(), localizedErrorMessage);}return dto;}private String resolveLocalizedErrorMessage(FieldError fieldError) {Locale currentLocale = LocaleContextHolder.getLocale();String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);//If the message was not found, return the most accurate field error code instead.//You can remove this check if you prefer to get the default error message.if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {String[] fieldErrorCodes = fieldError.getCodes();localizedErrorMessage = fieldErrorCodes[0];}return localizedErrorMessage;}
}
这就对了。 让我们花点时间评估我们刚刚完成的工作。
我们就快到了
现在,我们已使用Spring MVC 3.1将验证添加到REST API中。 与旧方法相比,此实现有一个主要好处:
我们可以使用@Valid批注来触发验证过程。
但是,仅当从包含异常处理程序方法的控制器类中抛出配置的异常时,才会触发使用@ExceptionHandler注释进行注释的方法。 这意味着如果我们的应用程序具有多个控制器,则必须为控制器创建一个公共基类,并将处理验证错误的逻辑移到该类。 这听起来似乎没什么大不了,但是我们应该更喜欢组合而不是继承 。
Spring MVC 3.2提供了可用于从控制器中消除继承需求的工具。 让我们继续前进,找出实现方法。
Spring MVC 3.2抢救
Spring MVC 3.2引入了一个新的@ControllerAdvice批注 ,我们可以使用该批注实现一个异常处理程序组件,该组件处理控制器抛出的异常。 我们可以通过执行以下步骤来实现此组件:
- 从CommentController类中删除处理验证错误的逻辑。
- 创建一个新的异常处理程序类,并将处理验证错误的逻辑移至创建的类。
在以下小节中将更详细地说明这些步骤。
从我们的控制器中删除异常处理逻辑
我们可以按照以下步骤从控制器中删除异常处理逻辑:
- 从CommentController类中删除MessageSource字段。
- 从我们的控制器类中删除构造函数。
- 从我们的控制器类中删除processValidationError()方法和私有方法。
CommentController类的源代码如下所示:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;@Controller
public class CommentController {@RequestMapping(value = "/api/comment", method = RequestMethod.POST)@ResponseBodypublic CommentDTO add(@Valid @RequestBody CommentDTO comment) {return comment;}
}
下一步是创建异常处理程序组件。 让我们看看这是如何完成的。
创建异常处理程序组件
我们可以按照以下步骤创建异常处理程序组件:
- 创建一个名为RestErrorHandler的类,并使用@ControllerAdvice批注对其进行批注。
- 将一个MessageSource字段添加到RestErrorHandler类。
- 通过使用构造函数注入来注入MessageSource bean。
- 将processValidationError()方法和所需的私有方法添加到RestErrorHandler类。
RestErrorHandler类的源代码如下所示:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;import java.util.List;
import java.util.Locale;@ControllerAdvice
public class RestErrorHandler {private MessageSource messageSource;@Autowiredpublic RestErrorHandler(MessageSource messageSource) {this.messageSource = messageSource;}@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {BindingResult result = ex.getBindingResult();List<FieldError> fieldErrors = result.getFieldErrors();return processFieldErrors(fieldErrors);}private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {ValidationErrorDTO dto = new ValidationErrorDTO();for (FieldError fieldError: fieldErrors) {String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);dto.addFieldError(fieldError.getField(), localizedErrorMessage);}return dto;}private String resolveLocalizedErrorMessage(FieldError fieldError) {Locale currentLocale = LocaleContextHolder.getLocale();String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);//If the message was not found, return the most accurate field error code instead.//You can remove this check if you prefer to get the default error message.if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {String[] fieldErrorCodes = fieldError.getCodes();localizedErrorMessage = fieldErrorCodes[0];}return localizedErrorMessage;}
}
我们终于到了
感谢Spring MVC 3.2,我们现在实现了一种优雅的解决方案,其中验证由@Valid批注触发,并且异常处理逻辑移至单独的类。 我认为我们可以称之为一天,享受工作成果。
摘要
这篇博客文章告诉我们
- 如果要在使用Spring 3.0时向REST API添加验证,则必须自己实现验证逻辑。
- Spring 3.1通过使用@Valid批注将验证添加到REST API成为可能。 但是,我们必须创建一个包含异常处理逻辑的公共基类。 每个需要验证的控制器都必须扩展此基类。
- 当使用Spring 3.2时,我们可以使用@Valid批注来触发验证过程,并将异常处理逻辑提取到单独的类中。
Github上提供了此博客的示例应用程序( Spring 3.1和Spring 3.2 )
翻译自: https://www.javacodegeeks.com/2013/05/spring-from-the-trenches-adding-validation-to-a-rest-api.html
rest api