处理自定义转换器抛出的 Spring Boot REST 异常

Posted

技术标签:

【中文标题】处理自定义转换器抛出的 Spring Boot REST 异常【英文标题】:Handling exception in Spring Boot REST thrown from custom converter 【发布时间】:2016-07-11 10:57:42 【问题描述】:

我是 Spring Boot 的新手,但是在阅读了几个小时关于 Spring Boot REST 中异常处理的帖子和博客之后,没有人写任何关于处理自定义转换器抛出的此类异常的文章,我决定在这里写。

我基于简单地从 IntelliJ 生成的 Spring Boot 开发小型 REST 应用程序。示例方法如下所示

@RestController
@RequestMapping("/resources")
public class CVResourceService 

    private final TechnologyRepository technologyRepository;
    private final ProjectRepository projectRepository;

    @Autowired
    public CVResourceService(TechnologyRepository technologyRepository,     ProjectRepository projectRepository) 
        this.technologyRepository = technologyRepository;
        this.projectRepository = projectRepository;
    

    @RequestMapping(value = "/users/guid/projects/langs/lang", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public Collection getUserProjects(@PathVariable("guid") GUID userGUID,         @PathVariable("lang") Language language) 
        return  ProjectDTOAssembler.toDTOs(projectRepository.findOne(userGUID, language));
    

因为guidlang 都是字符串,我希望这些信息从一开始就具有强类型,所以我为GUIDLanguage 类型创建了简单的转换器并将其注册到Application 类中:

public final class GUIDConverter implements Converter

    @Override
    public GUID convert(String source) 
        return GUID.fromString(source);
    


public class LanguageConverter implements Converter

    @Override
    public Language convert(String source) 
        Language language = Language.of(source);
        if (language == null)  throw new WrongLanguagePathVariableException(); 

        return language;
    

GUID 从方法工厂抛出异常,

...
public static GUID fromString(String string) 
    String[] components = string.split("-");

    if (components.length != 5)
        throw new IllegalArgumentException("Invalid GUID string: " + string);

    return new GUID(string);

...

Language return null 所以我从转换器抛出自定义异常。 在应用程序中注册:

@SpringBootApplication
public class Application extends WebMvcConfigurerAdapter 

    @Override
    public void addFormatters(FormatterRegistry registry) 
        registry.addConverter(new GUIDConverter());
        registry.addConverter(new LanguageConverter());
    

    public static void main(String[] args) 
       SpringApplication.run(Application.class, args);
   

使用@ResponseStatus@ControllerAdvice@ExpectationHandler 处理所有类型的异常我无法在 Controller 中捕获转换器的异常来重写(或更好的映射)“状态”、“错误”、“异常”和json 错误响应原始字段对我的值的“消息”。可能是因为在调用我的 REST 方法之前引发了异常。我也尝试了ResponseEntityExceptionHandler 的解决方案,但它根本不起作用。

对于正确语言为en 的请求http://localhost:8080/resources/users/620e643f-406f-4c69-3f4c-3f2c303f3f3f/projects/langs/end,响应异常为:


    "timestamp": 1458812172976,
    "status": 400,
    "error": "Bad Request",
    "exception":   "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException",
    "message": "Failed to convert value of type [java.lang.String] to required type [com.cybercom.cvdataapi.domain.Language]; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.PathVariable com.cybercom.cvdataapi.domain.Language] for value 'end'; nested exception is com.cybercom.cvdataapi.interfaces.rest.converter.WrongLanguagePathVariableException",
    "path": "/resources/users/620e643f-406f-4c69-3f4c-3f2c303f3f3f/projects/langs/end"

自定义异常仅在message 字段的最后一个位置,但当然应该与我的自定义消息交换。自定义异常应该在exception 字段中,现在是 Spring 异常。这当然是目标,但不知道如何在这种情况下实现它。

请解决我的问题,即捕获转换器抛出的异常并将它们映射为可以使用@ControllerAdvice 和控制器抛出的异常的方式。 提前谢谢。

【问题讨论】:

请在您的问题中添加例外 【参考方案1】:

当您的转换器引发异常时,@ControllerAdvice 类(或其他类型的异常处理程序)将拾取的实际异常是 MethodArgumentTypeMismatchException

如果您将转换器配置为选择MethodArgumentTypeMismatchException,那么它应该可以工作。

例如:

@ControllerAdvice
public class ExceptionConfiguration extends ResponseEntityExceptionHandler 

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<String> handleConverterErrors(MethodArgumentTypeMismatchException exception) 
        Throwable cause = exception.getCause() // First cause is a ConversionException
                                   .getCause(); // Second Cause is your custom exception or some other exception e.g. NullPointerException
        if(cause.getClass() == UnauthorizedException.class) 
            return ResponseEntity.status(401).body("Your session has expired");
        
        return ResponseEntity.badRequest().body("Bad Request: [" + cause.getMessage() + "]");
    

在上述情况下,如果我的自定义异常被抛出,响应将如下所示:

状态:401

您的会话已过期

否则会出现其他异常

状态:400

错误请求:[异常消息]

您可以根据需要自定义响应并返回 JSON。

要获取path,您可以将HttpServletRequest 注入到您的错误处理方法中

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<String> 
handleConverterErrors(HttpServletRequest req, MethodArgumentTypeMismatchException exception) 
    String path = req.getPathInfo()
    //...

【讨论】:

【参考方案2】:

你是对的 - @ControllerAdvice 等只捕获源自控制器方法内部的异常 - 老实说,我发现需要一些思考才能理解所有错误处理并组合一致的错误处理方法(没有必须在不同的地方重复错误处理)。

因为在我的应用程序中,我不可避免地需要捕获控制器之外的此类错误(或自定义 404 等),所以我只是进行应用程序范围的错误映射 - 我假设您将应用程序作为 JAR 运行,因为您已经定义了main() 应用程序中的方法。

为了便于阅读,我的偏好是有一个特定的错误配置文件,但如果你愿意,你可以在你的配置中将它定义为一个普通的 bean:

@Configuration
class ErrorConfiguration implements EmbeddedServletContainerCustomizer 

   /**
     * Set error pages for specific error response codes
     */
   @Override public void customize( ConfigurableEmbeddedServletContainer container ) 
       container.addErrorPages( new ErrorPage( HttpStatus.NOT_FOUND, "/errors/404" ) )
   


您可以根据特定的异常类型以及 Http 响应代码映射错误页面,因此它非常灵活 - 但我通常做的是定义自定义异常并将 Http 响应代码附加到异常 - 这样我就可以映射 @ 987654325@ 代码在我的错误配置中,然后如果我想在代码中的任何地方,例如,404 请求我可以只是 throw new PageNotFoundException() (但那是个人喜好)。

@ResponseStatus(value = HttpStatus.NOT_FOUND)
class PageNotFoundException extends Exception  

映射路径(在上面的示例中为“/errors/404”)映射到普通控制器,这很好,因为它可以让您在给定错误上执行您可能想要执行的任何错误记录/处理/发送电子邮件等- 这样做的缺点是,由于它是处理错误的标准控制器,因此您可能有一个暴露的端点,如 /errors/404 - 这并不理想(有一些选项 - 隐藏 URL,因此不太可能被发现,或使用 apache 之类的东西来防止直接访问这些端点等)

我简要介绍了它here - 包括在将您的应用程序作为 WAR 托管在传统的 tomcat 服务器中时它是如何工作的详细信息

【讨论】:

以上是关于处理自定义转换器抛出的 Spring Boot REST 异常的主要内容,如果未能解决你的问题,请参考以下文章