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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了处理从自定义转换器抛出的Spring Boot REST中的异常相关的知识,希望对你有一定的参考价值。

我是Spring Boot的新手,但经过几个小时的阅读关于Spring Boot REST中异常handlig的帖子和博客,没有人写任何关于处理从自定义Converter抛出的异常的事情,我决定写这里。

我开发了基于Spring Boot的小型REST应用程序,它只是从IntelliJ生成的。示例性方法看起来像这样

@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返回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错误响应原始字段的“status”,“error”,“exception”和“message”我的价值观。可能是因为在调用REST方法之前抛出了异常。我尝试用ResponseEntityExceptionHandler解决方案,但它根本不起作用。

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

{
    "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和从控制器抛出的异常。 Thx提前。

答案

你是对的 - @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响应代码附加到异常 - 这样我可以在我的映射中使用HttpStatus代码错误配置,然后如果我想要的代码中的任何地方,为exaple,404一个请求我可以只throw new PageNotFoundException()(但这是个人喜好)。

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

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

我简要地写了一下here - 包括在传统的tomcat服务器中将你的应用程序托管为WAR时的工作原理的详细信息

另一答案

当您的转换器抛出异常时,@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()
    //...
}

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