【问题标题】:Handling custom exceptions (i18n) with Spring Data REST使用 Spring Data REST 处理自定义异常 (i18n)
【发布时间】:2017-11-28 01:04:49
【问题描述】:

我正在使用 Spring Boot 1.5.4 和 Spring JPA、Spring Data REST、HATEOAS... 我正在寻找一种最佳实践(Spring 方式)来自定义异常 Spring 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 REST 异常非常好:

    {
    "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 文档中没有找到关于如何自定义或本地化异常 (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;
}

我想我可以通过这种方式拦截异常:

    @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 最佳实践将所有内容组合在一起?

【问题讨论】:

    标签: java spring spring-mvc spring-data-rest


    【解决方案1】:
    @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);
        }
    

    参见春季文档7.15.1 Internationalization using MessageSource


    " 我应该如何创建 exceptionMessageObject 就像一个 Spring 数据 REST 创建? "

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

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

    “如何针对不同的异常有不同的消息?我应该 创建一个长的 if else 链检查异常的类? "

    创建解析器,

    private String resolveExceptionToMessage(Exception exceptio){
        //or put in map<Exceptio,String error.message.type1> 
        // 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<Object>(customError, new HttpHeaders(), 
                              customError.getStatus());
        }
    

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

    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<Object>(customError, new HttpHeaders(), 
                          customError.getStatus());
    }
    

    【讨论】:

    • 我应该如何创建 exceptionMessageObject 就像 Spring Data REST 创建的一样?如何为不同的异常提供不同的消息?我应该创建一个长的 if else 链来检查异常的类吗?如何设置httpStatus?我想使用默认状态 Spring Data REST 用于公共异常...谢谢
    • 我不知道你没有基本的东西来 return new ResponseEntity(exceptionMessageObject, new HttpHeaders(), httpStatus); .我更新了我的答案
    • 感谢您的回复。我将其标记为答案。我发布了另一种方法,自定义 ErrorAttributes,在我看来非常优雅。
    【解决方案2】:

    感谢@sbjavateam 的回复。为了完整起见,我想发布另一种方法来做这件事。评论不适合写所有东西,所以我在这里回复。

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

    public class CustomErrorAttributes extends DefaultErrorAttributes {
    
        private Logger log = LogManager.getLogger();
    
        @Autowired
        private MessageSource messageSource;
    
        @Override
        public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
            Locale locale = LocaleContextHolder.getLocale();
            Map<String, Object> 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<String, Object> 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;
        }
    }
    

    通过这种方式自定义异常非常容易,您可以只覆盖值而不是创建新的自定义异常对象。

    【讨论】:

    • 你应该确定消息文件中有 throwable.getClass().getName()。
    • 当然,这是我的项目决定。我也使用 messageSource.setUseCodeAsDefaultMessage(true);因此,如果找不到,则不会翻译该消息。
    【解决方案3】:

    在我的项目中,我使用 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<Object> error(HttpServletRequest request) {
        HashMap<String, Object> response = new HashMap<String, Object>();
        // your code here...
        return new ResponseEntity<Object>(response, status);
    }
    }
    

    【讨论】:

      猜你喜欢
      • 2013-07-02
      • 2021-07-09
      • 1970-01-01
      • 1970-01-01
      • 2014-02-04
      • 2016-08-20
      • 2014-02-21
      • 1970-01-01
      • 2021-07-10
      相关资源
      最近更新 更多