第三十二讲-Tomcat 异常处理

第三十二讲-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 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 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> error(HttpServletRequest request) {

HttpStatus status = getStatus(request);

if (status == HttpStatus.NO_CONTENT) {

return new ResponseEntity<>(status);

}

Map 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 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 model, HttpServletRequest request, HttpServletResponse response) throws Exception {

System.out.println(model);

response.setContentType("text/html;charset=utf-8");

response.getWriter().print("""

服务器内部错误

""");

}

};

}

// 创建一个视图解析器,用于解析我们自己创建的视图

@Bean

public ViewResolver viewResolver() {

return new BeanNameViewResolver();

}

重新启动Tomcat,测试一下:

除此之外,我们也可以照葫芦画瓢可以做出更加丰富的额扩展,例如返回JSON格式的异常数据、XML格式的异常数据等等。这里呢,就不再一一演示,办法也很简单,只需要将自定义的错误视图的响应类型做相应的修改即可以实现。

相关推荐

在 iPhone 或 iPad 上管理和删除联系人
bet体育365官网正规平台

在 iPhone 或 iPad 上管理和删除联系人

📅 06-29 👁️ 1651
阴阳师跳跳哥哪里最多
365bet手机在线投注

阴阳师跳跳哥哪里最多

📅 07-13 👁️ 1671
2200日元兑换人民币
365bet手机在线投注

2200日元兑换人民币

📅 08-26 👁️ 4517
sam机架效果包全套 V1.0 免费版
365平台是做什么的

sam机架效果包全套 V1.0 免费版

📅 07-30 👁️ 4843
为什么盐可以去除茶垢(茶渍的神奇去除法)
365平台是做什么的

为什么盐可以去除茶垢(茶渍的神奇去除法)

📅 08-11 👁️ 754
首都医科大学附属北京安贞医院
365bet手机在线投注

首都医科大学附属北京安贞医院

📅 08-17 👁️ 5005