第三十二讲-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()

此时有一个路径映射,接着我们用浏览器来访问一下:

image-20240816181818092

我们发现,这个错误异常页面是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()

image-20240816184021097

这样就把我们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)

image-20240816185803052

我们发现,异常类是由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,测试一下:

image-20240816190759430

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

posted @   LilyFlower  阅读(88)  评论(0编辑  收藏  举报
编辑推荐:
· .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
点击右上角即可分享
微信分享提示