2

我正在编写一个使用 RestTemplate 进行下游调用的 Web 应用程序。如果底层服务返回 401 Unauthorized,我想也向调用应用程序返回 401;默认行为是返回 500。我想保留由提供的默认 Spring Boot 错误响应BasicErrorController;我想要的唯一更改是设置状态码。

在自定义异常中,我只是用 注释异常类@ResponseStatus,但我不能在这里这样做,因为HttpClientErrorException.Unauthorized它是由 Spring 提供的。我尝试了两种方法@ControllerAdvice

@ExceptionHandler(HttpClientErrorException.Unauthorized.class)
@ResponseStatus(UNAUTHORIZED)
public void returnsEmptyBody(HttpClientErrorException.Unauthorized ex) {
}

@ExceptionHandler(HttpClientErrorException.Unauthorized.class)
@ResponseStatus(UNAUTHORIZED)
public void doesNotUseBasicErrorController(HttpClientErrorException.Unauthorized ex) {
    throw new RuntimeException(ex);
}

我如何配置 MVC 以继续使用所有内置的引导错误处理,除了显式覆盖状态代码?

4

2 回答 2

2

下面的代码对我有用——在一个由@RestController一个方法组成的应用程序中throw new HttpClientException(HttpStatus.UNAUTHORIZED),在嵌入式 Tomcat 上运行。如果您在非嵌入式 Tomcat 上运行(或者,我怀疑,在嵌入式非 Tomcat 上),您可能必须做一些至少有所不同的事情,但我希望这个答案至少有点帮助。

@ControllerAdvice
public class Advisor {
  @ExceptionHandler(HttpClientException.class)
  public String handleUnauthorizedFromApi(HttpClientException ex, HttpServletRequest req) {
    if (/* ex instanceof HttpClientException.Unauthorized or whatever */) {
      req.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 401);
    }
    return "forward:/error";
  }
}

解释:当我们处理请求 X(在嵌入式 servlet 中)时抛出 HttpClientException 时,通常会发生这种情况,它会一直冒泡到某个 org.apache 类。(我可能会再次启动调试器并确定是哪一个,但这是一个非常高级的解释,所以这并不重要。)然后该类将请求 X 发送回应用程序,除非这次请求发送到“/error”,而不是它最初要去的地方。在 Spring Boot 应用程序中(只要您不关闭某些自动配置),这意味着请求 X 最终由BasicErrorController.

好的,那么除非我们做点什么,否则为什么整个系统会向客户端发送 500 呢?因为上面提到的那个 org.apache 类在请求 X 上设置了一些东西,上面写着“处理这个出错了”。这样做是正确的:毕竟,处理请求 X 确实导致了 servlet容器必须捕获的异常。就容器而言,应用程序搞砸了。

所以我们想做几件事。首先,我们希望 servlet 容器不会认为我们搞砸了。我们通过告诉 Spring 在它到达容器之前捕获异常来实现这一点,即通过编写一个@ExceptionHandler方法。其次,即使我们捕获了异常,我们也希望请求转到“/error”。我们通过一个简单的方法来实现这一点,我们自己通过转发将它发送到那里。第三,我们希望在BasicErrorController它发送的响应中设置正确的状态和消息。事实证明,BasicErrorController(与其直接超类一起工作)查看请求的属性以确定要发送给客户端的状态代码。(弄清楚这一点需要阅读该类的源代码,但该源代码在 github 上并且完全可读。)因此我们设置了该属性。

编辑:我写这个有点忘乎所以,忘了提到我不认为使用这段代码是好的做法。它将您与 的一些实现细节联系在一起BasicErrorController,而这不是 Boot 类的预期使用方式。Spring Boot 通常假设您希望它完全或根本不处理您的错误;这也是一个合理的假设,因为零碎的错误处理通常不是一个好主意。我给你的建议——即使上面的代码(或类似的代码)确实可以工作——是编写一个@ExceptionHandler完全处理错误的代码,这意味着它同时设置状态和响应正文并且不转发任何内容。

于 2020-09-15T13:14:33.560 回答
0

您可以自定义 RestTemplate 的错误处理程序以引发您的自定义异常,然后使用您提到的 @ControllerAdvice 处理该异常。

像这样的东西:

@Configuration
public class RestConfig {
    
    @Bean
    public RestTemplate restTemplate(){
        
        // Build rest template
        RestTemplate res = new RestTemplate();
        res.setErrorHandler(new MyResponseErrorHandler());
        return res;
    }
    
    private class MyResponseErrorHandler extends DefaultResponseErrorHandler {

        @Override
        public void handleError(ClientHttpResponse response) throws IOException {
            if (HttpStatus.UNAUTHORIZED.equals(response.getStatusCode())) {
                // Throw your custom exception here
            }
        }
    }
}
于 2020-09-14T23:10:20.827 回答