如何通过Boot仅在Spring MVC错误上更改状态代码?

问题描述

我正在编写一个Web应用程序,该应用程序使用RestTemplate进行下游调用。如果基础服务返回401未经授权,则我还想将401返回给调用应用程序;认行为是返回500。我想保留BasicErrorController提供的认Spring Boot错误响应;我唯一想要的更改是设置状态代码

自定义异常中,我只是用@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以继续使用所有内置的Boot错误处理 来显式覆盖状态代码

解决方法

以下代码对我有用-在一个由@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发送回应用程序,除了这次请求转到“ /错误”,而不是最初的目标。在Spring Boot应用程序中(只要您不关闭某些自动配置),这意味着请求X最终将由BasicErrorController中的某种方法处理。

好的,那为什么整个系统为什么不发送500到客户,除非我们做些什么?因为上面提到的org.apache类在请求X上设置了一些内容,该内容表示“处理错误”。这样做是正确的:毕竟,处理请求X确实导致了servlet 容器必须捕获的异常。就容器而言,该应用程序混乱了。

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

编辑:我对此写了一些文章,忘了提到我不认为使用此代码是一种好习惯。它使您与BasicErrorController的一些实现细节相关联,而这并不是Boot类的预期使用方式。 Spring Boot通常假设您希望它完全或根本不处理错误;这也是一个合理的假设,因为零碎的错误处理通常不是一个好主意。我对您的建议-即使上面的代码(或类似的代码)确实可以正常工作-还是写一个@ExceptionHandler来完全处理错误,这意味着它同时设置了状态和响应主体,并且没有设置转发任何东西。

,

您可以自定义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
            }
        }
    }
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...