【问题标题】:Spring MVC Rest Service Controller with Error Handling done right?错误处理的 Spring MVC Rest 服务控制器正确吗?
【发布时间】:2013-04-30 08:13:56
【问题描述】:

我想知道如何正确实现一个应该用作 REST 服务的 Spring Controller。特别是我想尝试使界面尽可能地 RESTful。我还想使用 HTTP 错误代码,以便我的客户可以采取相应的行动。

我想知道如何实现我的方法,所以如果一切正常(在响应的正文中)它们会返回 JSON,或者抛出一个 http 错误代码以及它不起作用的自定义原因(可能来自的错误DAO 或数据库)。但是我不确定哪一个是正确的方法?返回一个字符串并添加值以返回一个模型,或者返回一个 HashMap 并将我的东西放在那里?还是直接返回对象?但是如果发生错误并且我无法返回所述类怎么办?而是返回null? 我发布了我可以想象的 2-3 种方法:

@RequestMapping(value="/addUser", method= RequestMethod.POST)
public String addUser(@RequestBody User user, HttpServletResponse response, Model model) throws Exception{

    try{
        userService.addUser(user);
        model.addAttribute("user", userService.getUser(user.getUsername(), user.getPassword()));
        return "user";
    }catch(Exception e){
        model.addAttribute("error", e.toString());
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
        return "error";
    }
}

或者更确切地说是这样:

@RequestMapping(value="/addUser", method= RequestMethod.POST)
public @ResponseBody Map addUser(@RequestBody User user, HttpServletResponse response){
    Map map = new HashMap();
    try{
        userService.addUser(user);
        map.put("success", true);
        map.put("username", user.getUsername());
    }catch (KeyAlreadyExistsException e){
        map.put("success", false);
        map.put("Error", e.toString());
        response.sendError(HttpServletResponse.SC_FORBIDDEN, e.toString());
    }catch(Exception e){
        map.put("success", false);
        map.put("Error", e.toString());
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
    }
    finally {
        return map;
    }
}

我意识到代码不是“恰到好处”,但我不知道如何使它成为它需要的方式。也许一些经验的回应会有所帮助?感谢您的支持

【问题讨论】:

    标签: java spring rest spring-mvc


    【解决方案1】:

    您还可以在 Rest Controller 中使用 @ExceptionHandler 注释方法捕获异常。

    @ExceptionHandler(Exception.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public String handleException(Exception e) {
        return "return error object instead";
    }
    

    这将使您的实际控制器/业务逻辑更清晰。

    【讨论】:

    • 我喜欢这个主意,实际上我就是这么做的!我按照 Bart 的建议创建了一个错误资源,然后在此方法中处理 ctrl 中的所有错误。我可能可以进一步概括和处理应用程序范围内的错误,但一步一步来。
    • 您可以在控制器中为每个异常类型创建多个异常处理程序。
    • 我发现如果我每个控制器只有一个异常处理程序方法而不是它们的整个数组,然后检查在此处理程序中引发了哪个错误,它看起来更干净
    • 好吧,我更喜欢 per-exception-exception-resolver 方法 :-) 您可以轻松处理异常层次结构
    • 小心这种方法,因为这将隐藏 Spring 的内部异常处理,例如 MissingPathVariableException 或 ConversionNotSupportedException 并返回状态代码 500 而不是 400。如果为 Exception.class 添加处理程序,请确保扩展类 ResponseEntityExceptionHandler。
    【解决方案2】:

    首先,我认为在返回 JSON 时应该始终返回一个对象。即使出现可怕的错误。

    当出现问题时,您只需设置 response.setStatus() 并返回描述错误的资源。

    public class ErrorResource implements Resource {
        private final int status;
        private final String message;
    
        public ErrorResource(int s, String m) {
            status = s;
            message = m;
        }
    
        public int getStatus() {
            return status;
        }
    
        public String getMessage() {
            return message;
        }
    }
    

    资源被序列化,结果是

    {"status":500, "message":"Yay!"}
    

    使用Map 可以,但我想建议您编写一些资源类来定义要返回的对象。它们会更容易维护。 Maps 不提供任何结构,而结构是创建 REST 服务时非常重要的部分。

    我认为您不应该返回嵌入了原始异常消息的资源。它可能会泄露您不希望任何人看到的信息。

    【讨论】:

    • 所以你会说我有一个主类“returnRessource”,我打算从中返回所有其他类,无论错误或实际值是否应该继承?否则,如果您有一个方法,可以说public User getUser(){...},我真的不明白您打算怎么做,而且它吹了,您不能只返回另一个类,您必须返回 User 类。还是你觉得应该是public Object getUser(){...}??
    • 我可能还应该补充一点,我打算使用 Angular 编写我的端点。这里根据http响应码操作非常方便。但是我仍然同意 json 对象比默认的 tomcat 错误页面更好。那么如何返回一个对象而不是 response.sendError 方法?我的意思是虽然仍然设置了 http 错误代码
    • 您可以创建一个接口Resource 并将其用作返回类型。如果一个名为User 的类可以作为资源返回,它应该实现接口。所以它变成了public Resource getUser(){...}。由于ErrorResource 也实现了相同的接口。您可以毫不费力地退货。
    • 这看起来有点好,但也让我不得不将我的实体代码与我的控制器问题混合在一起。我想返回一个对象用户,它也被持久化到数据库中,必须让该对象实现一个接口,使我的控制器代码“绿色”在我看来不是那么干净。我知道这是可行的,但很好,只是感觉不对。
    • 但我喜欢可以放入响应正文的 errorressource 类的想法。加上 reagten 的回答就完成了
    【解决方案3】:

    您可以将@ExceptionHandler@ControllerAdvice 一起使用,请查看link

    【讨论】:

      【解决方案4】:

      使用 ResponseEntity 类来利用带有 http 状态代码的错误。

      你可以试试下面的代码:

      @RequestMapping(value = "/profile", method = RequestMethod.GET) 
      @ResponseBody @ResponseStatus(value = HttpStatus.OK) 
      
      public ResponseEntity<UserVO> getUserProfile() 
      { 
         string userName = getUserAuthentication().getName(); 
         if (StringUtils.isEmpty(userName)) RestUtil.defaultJsonResponse(""); 
         User user = userService.getUserByUserNameWithCounters(userName); 
         return RestUtil.getJsonResponse(new UserVO(user)); 
      }
      

      【讨论】:

      • 添加更多解释。
      • 对不起,我没有很好地格式化,但试试这个代码 -> @RequestMapping(value = "/profile", method = RequestMethod.GET) @ResponseBody @ResponseStatus(value = HttpStatus.OK) public ResponseEntity&lt;UserVO&gt; getUserProfile() { String userName = getUserAuthentication().getName(); if (StringUtils.isEmpty(userName)) RestUtil.defaultJsonResponse(""); User user = userService.getUserByUserNameWithCounters(userName); return RestUtil.getJsonResponse(new UserVO(user)); }
      【解决方案5】:

      如果您希望将带有 stackTrace 的整个异常传输到您的客户端,正如@Bart 所说,您应该发送一个“ErrorResource”对象。

      图书馆有现成的:

      <dependency>
        <groupId>com.github.zg2pro</groupId>
        <artifactId>spring-rest-basis</artifactId>
        <version>0.2</version>
      </dependency>
      

      将其添加到您的项目中,然后将“@ControllerAdvice”类添加到您的 bean,如 project wiki 中所述。

      这应该可以很好地处理您的错误!

      【讨论】:

        猜你喜欢
        • 2016-11-06
        • 2014-09-02
        • 1970-01-01
        • 2016-07-06
        • 2014-02-10
        • 1970-01-01
        • 2016-04-16
        • 2017-02-24
        • 2015-05-15
        相关资源
        最近更新 更多