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

标签 java rest exception-handling spring-boot converter

我是 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 都是字符串,我希望这些信息从一开始就是强类型的,所以我为 GUID 创建了简单的转换器> 和 Language 类型并将其注册到 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 错误响应原始字段的“状态”、“错误”、“异常”和“消息”重写(或更好地映射)为我的值。可能是因为异常是在我的 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 异常(exception)。这当然是目标,但不知道如何在这种情况下实现它。

请解决我的问题,捕获从转换器抛出的异常并将它们映射为可以使用 @ControllerAdvice 完成的方式以及从 Controller 抛出的异常。 提前致谢。

最佳答案

你是对的 - @ControllerAdvice 等只捕获源自 Controller 方法内部的异常 - 老实说,我发现你需要一些思考才能理解所有错误处理并将它们放在一起一致的错误处理方法(无需在不同地方重复错误处理)。

因为在我的应用程序中,我不可避免地需要捕获 Controller 外部的此类错误(或自定义 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 我的错误配置中的代码,然后如果我想在代码中的任何地方,例如 404 一个请求,我可以 throw new PageNotFoundException() (但那是个人偏好).

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

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

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

关于java - 处理从自定义转换器抛出的 Spring Boot REST 中的异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36190246/

相关文章:

ios - EXC_RESOURCE 崩溃并不总是显示在 iOS 8 上,有很多线程唤醒?

java - resize() 方法被调用了多少次?

java - TypeScript 与 Java 中的接口(interface)

java - 从我的 Java Web 服务器向 Android 手机发送推送通知

java - 如何在请求正文中将可序列化对象的映射作为 JSON 传递

Java - 访问 Tomcat/Jersey url 给出 404 Not Found

rest - 使用 Powershell 和 Invoke-WebRequest 将 2.5 GB 上传到 Web 服务器

python - 异常消息(Python 2.6)

java - finish(); 之间的区别和 onBackPressed();

exception-handling - ASP.NET 5 分层记录异常的最佳实践