第三十二讲-Tomcat 异常处理
第三十二讲-Tomcat 异常处理
我们前面讲的@ControllerAdvice配合@ExceptionHandler可以处理异常,但是实际上这套组合处理的异常是非常有限的。为什么这么说呢?如果是Spring MVC框架控制器抛出的异常,那么最终可以是由@ControllerAdvice处理的。但是是如果是Tomcat抛出的异常,例如过滤器抛出的异常,@ControllerAdvice就无法处理了,我们就需要一个更上一层的异常处理者-Tomcat。本讲我们来看看Tomcat异常是如何处理的。
我们首先看看下面的示例代码:
public class A32 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
handlerMapping.getHandlerMethods().forEach((RequestMappingInfo k, HandlerMethod v) -> {
System.out.println("映射路径:" + k + "\t方法信息:" + v);
});
}
}
@Configuration
public class WebConfig {
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
@Bean // @RequestMapping
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
@Bean // 注意默认的 RequestMappingHandlerAdapter 不会带 jackson 转换器
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
return handlerAdapter;
}
@Controller
public static class MyController {
@RequestMapping("test")
public ModelAndView test() {
int i = 1 / 0;
return null;
}
}
}
我们来测试一下:
映射路径:{ [/test]} 方法信息:com.cherry.a32.WebConfig$MyController#test()
此时有一个路径映射,接着我们用浏览器来访问一下:
我们发现,这个错误异常页面是Tomcat提供的。而且还是html格式的。但是呢,在开发过程中,我们有时候会将错误信息以JSON的格式返回个客户端,那么怎么定制错误响应内容呢?我们来尝试编写以下。
首先我们添加一个Bean,该Bean用于指定Tomcat在运行过程中如果出错了,发生错误的处理路径是什么:
@Bean // 修改 Tomcat 服务器默认错误地址
public ErrorPageRegistrar errorPageRegistrar() { // 出现错误,会使用请求转发 forward 跳转到 error 地址
return webServerFactory -> webServerFactory.addErrorPages(new ErrorPage("/error"));
}
再添加一个BeanPostProcessor,专门用于处理错误页面Bean的Bean后处理器:
@Bean
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
return new ErrorPageRegistrarBeanPostProcessor();
}
添加一个控制器映射,专门用于处理Tomcat发生错误时使用的控制器
@RequestMapping("/error")
@ResponseBody
public Map<String, Object> error(HttpServletRequest request) {
Throwable e = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
return Map.of("error", e.getMessage());
}
成功启动测试:
映射路径:{ [/error]} 方法信息:com.cherry.a32.WebConfig$MyController#error(HttpServletRequest)
映射路径:{ [/test]} 方法信息:com.cherry.a32.WebConfig$MyController#test()
这样就把我们Tomcat错误页面做成了自定义,让错误信息以JSON格式返回给了浏览器。
我们讲了Tomcat的异常处理,就不得不提Spring Boot中的BasicErrorController这个类,这个类的作用和我们刚才写的error
控制器是一样的,都是为了配合Tomcat错误路径作出相应的处理。
我们可以看一下BasicErrorController
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
// ......
// 可以返回HTML格式的异常信息
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
// 可以返回JSON格式的错误信息
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
// ....
}
这个BasicErrorController也是一个控制器,其路径默认是从配置文件中读取server.error.path
参数,如果该参数为空,会读取error.path
参数,如果这个参数还未空,就是使用/error
路径,我们发现这个BasicErrorController和我们刚才写的错误控制器有着异曲同工之处,较为相似。
下面呢,我们将我们自己写的错误控制器注释掉,使用BasicErrorController来写一个错误处理控制器:
// @RequestMapping("/error")
// @ResponseBody
// public Map<String, Object> error(HttpServletRequest request) {
// Throwable e = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// return Map.of("error", e.getMessage());
// }
// }
@Bean
public BasicErrorController basicErrorController() {
ErrorProperties errorProperties = new ErrorProperties();
// 返回的信息包含错误信息
errorProperties.setIncludeException(true);
// 需要错误属性(例如错误发生的时间,错误路径,异常等信息)和配置文件信息(从配置文件中读取错误相关的配置信息)
return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}
我们重启重新测试一下:
映射路径:{ [/error]} 方法信息:org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
映射路径:{ [/test]} 方法信息:com.cherry.a32.WebConfig$MyController#test()
映射路径:{ [/error], produces [text/html]} 方法信息:org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
我们发现,异常类是由Spring框架提供的。那么我们如何返回自定义格式的相关错误信息呢?我们可以自己设定错误视图对象/error
。
我们手动来创建一个view对象(注意视图名字最好奇error,否则起其它名字最好在配置文件中指定错误视图的名字)
@Bean
public View error() {
return new View() {
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println(model);
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("""
<h3>服务器内部错误</h3>
""");
}
};
}
// 创建一个视图解析器,用于解析我们自己创建的视图
@Bean
public ViewResolver viewResolver() {
return new BeanNameViewResolver();
}
重新启动Tomcat,测试一下:
除此之外,我们也可以照葫芦画瓢可以做出更加丰富的额扩展,例如返回JSON格式的异常数据、XML格式的异常数据等等。这里呢,就不再一一演示,办法也很简单,只需要将自定义的错误视图的响应类型做相应的修改即可以实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2021-08-16 计算机网络-4-6-路由选择协议RIP