如何解决如何通过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
}
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。