使用Spring Data REST处理自定义exception(i18n)

我正在使用Spring Boot 1.5.4与Spring JPA,Spring Data REST,HATEOAS ……我正在寻找最佳实践(Spring方式)来定制exceptionSpring Data REST正在管理添加i18n支持。

我查看了类MessageException( https://github.com/spring-projects/spring-data-rest/blob/master/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest /webmvc/support/ExceptionMessage.java )作为起点。

典型的Spring Data RESTexception非常好:

{ "timestamp": "2017-06-24T16:08:54.107+0000", "status": 500, "error": "Internal Server Error", "exception": "org.springframework.dao.InvalidDataAccessApiUsageException", "message": "org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved beforeQuery current operation : com.test.server.model.workflows.WorkSession.checkPoint -> com.test.server.model.checkpoints.CheckPoint; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved beforeQuery current operation : com.test.server.model.workflows.WorkSession.checkPoint -> com.test.server.model.checkpoints.CheckPoint", "path": "/api/v1/workSessions/start" } 

我想要做的是:

  1. 本地化错误和消息字段(i18n)
  2. 可能会将消息文本更改为其他内容(始终本地化)

我没有在Spring Data REST文档中找到有关如何自定义或本地化exception的任何参考( https://docs.spring.io/spring-data/rest/docs/current/reference/html/ )。 我希望有一种优雅的方式来做到这一点。

我在我的WebMvcConfigurerAdapter中添加了这个:

 @Bean public LocaleResolver localeResolver() { return new SmartLocaleResolver(); } public class SmartLocaleResolver extends CookieLocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String acceptLanguage = request.getHeader("Accept-Language"); if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) { return super.determineDefaultLocale(request); } return request.getLocale(); } } @Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource source = new ResourceBundleMessageSource(); source.setBasenames("i18n/messages"); // name of the resource bundle source.setUseCodeAsDefaultMessage(true); return source; } 

我想我能够以这种方式拦截exception:

  @ControllerAdvice(annotations = RepositoryRestController.class) public class GenericExceptionHandler { @ExceptionHandler public ResponseEntity handle(Exception e, Locale locale) { //missing part... return new ResponseEntity(exceptionMessageObject, new HttpHeaders(), httpStatus); } 

有没有办法使用Spring最佳实践将所有内容组合在一起?

 @ControllerAdvice(annotations = RepositoryRestController.class) public class GenericExceptionHandler { @Autowired private MessageSource messageSource; @ExceptionHandler //if you don't use Exception e in method you can remove it , live only Locale public ResponseEntity handle(Exception e, Locale locale) { String errorMessage = messageSource.getMessage( "error.message", new Object[]{},locale); //set message or return it instead of exceptionMessageObject exceptionMessageObject.setMessage(exceptionMessageObject); return new ResponseEntity(exceptionMessageObject, new HttpHeaders(), httpStatus); } 

请参阅spring doc 7.15.1使用MessageSource进行国际化


“我应该如何创建exceptionMessageObject,就像Spring Data REST创建的一样?”

创建自己的错误包装器,如:

 public class CustomError { private HttpStatus status; private String message; private Exception originalException;//if you need it // getter setter } 

“如何针对不同的exception使用不同的消息?我应该创建一个long if else链来检查exception的类吗?”

创建解析器,

 private String resolveExceptionToMessage(Exception exceptio){ //or put in map // String errorCode = map.get(excptio); //eturn messageSource.getMessage(errorCode , new Object[]{},locale); if(exceptio instanceof ....){ return messageSource.getMessage("error.message.type1", new Object[]{},locale); } return ""; } 

或者使用带有@ExceptionHandler({CustomException1.class}),@ ExceptionHandler({CustomException1.class})的方法….并且确实在每个方法中放入errror.code,所有其他部分都是相似的

  @ExceptionHandler({ CustomException1.class}) public ResponseEntity handleException1() { return createError(code for this exceptio 1); } @ExceptionHandler({ CustomException2.class}) public ResponseEntity handleException2() { return createError(code for this exceptio 2); } private ResponseEntity createError(String errorCode ) { CustomError customError = new CustomError(); customError.setHttpStatus(HttpStatus.BAD_REQUEST); String errorMessage = messageSource.getMessage( errorCode , new Object[]{},locale); customError.setMessage(errorMessage ); customError.setOriginalException(e); return new ResponseEntity(customError, new HttpHeaders(), customError.getStatus()); } 

如何设置httpStatus? 我想使用默认状态Spring Data REST用于公共exception…

 public ResponseEntity handle(Exception e, Locale locale) { CustomError customError = new CustomError(); customError.setHttpStatus(HttpStatus.BAD_REQUEST); customError.setMessage(resolveExceptionToMessage(e)); customError.setOriginalException(e); return new ResponseEntity(customError, new HttpHeaders(), customError.getStatus()); } 

感谢@sbjavateam的回复。 我想发布另一种方法来做这件事只是为了完整性。 评论并不是写一切的理想选择,所以我在这里回复。

而不是使用@ControllerAdvice,更简单的解决方案是自定义ErrorAttributes:

 public class CustomErrorAttributes extends DefaultErrorAttributes { private Logger log = LogManager.getLogger(); @Autowired private MessageSource messageSource; @Override public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Locale locale = LocaleContextHolder.getLocale(); Map errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace); Throwable throwable = getError(requestAttributes); /** * Adding the cause if present */ if (throwable != null && throwable.getCause() != null) { Throwable cause = throwable.getCause(); Map causeErrorAttributes = new HashMap<>(); causeErrorAttributes.put("exception", cause.getClass().getName()); causeErrorAttributes.put("message", cause.getMessage()); errorAttributes.put("cause", causeErrorAttributes); } /** * Customizing the message for every exception */ if (throwable instanceof InvalidDataAccessApiUsageException) { String message = messageSource.getMessage(throwable.getClass().getName(), new Object[] {}, locale); errorAttributes.put("message", message); } return errorAttributes; } } 

当然,您必须在WebMvcConfigurerAdapter中定义此bean或添加@Component。 在第一种情况下,您需要做:

 @EnableHypermediaSupport(type = { HypermediaType.HAL }) public class WebMvcConfiguration extends WebMvcConfigurerAdapter { @Bean public CustomErrorAttributes myCustomErrorAttributes() { return new CustomErrorAttributes(); } @Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasenames("classpath:/i18n/messages"); messageSource.setDefaultEncoding("UTF-8"); messageSource.setUseCodeAsDefaultMessage(true); messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1)); messageSource.setFallbackToSystemLocale(false); return messageSource; } } 

通过这种方式可以非常轻松地自定义exception,您只需覆盖值而不是创建新的自定义exception对象。

在我的项目中,我使用CustomErrorController。 此控制器缓存包括404在内的所有错误。示例:

 @Controller @RequestMapping("${error.path:/error}") public class CustomErrorController implements ErrorController { @Value("${error.path:/error}") private String errorPath; @Override public String getErrorPath() { return this.errorPath; } @RequestMapping @ResponseBody public ResponseEntity error(HttpServletRequest request) { HashMap response = new HashMap(); // your code here... return new ResponseEntity(response, status); } }