• Spring MVC 十:异常处理


    异常是每一个应用必须要处理的问题。

    Spring MVC项目,如果不做任何的异常处理的话,发生异常后,异常堆栈信息会直接抛出到页面。

    比如,我们在Controller写一个异常:

        @GetMapping(value="/hello",produces={"text/html; charset=UTF-8"})
        @ResponseBody
        public String hello(ModelAndView model){
            int c = 100 / 0;
            return "

    User info

    "; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    浏览器访问:
    在这里插入图片描述

    用户体验相当不好,所以一般情况下,应用必须要想办法处理异常。

    异常处理方式

    常见的异常处理方式,无非:

    1. 应用中对所有可能发生异常的地方,都try catch,捕获异常后做相应的处理。
    2. 集中处理异常。

    第一种方式显然不好,一方面是代码中需要到处都写try catch,万一某一段代码由于程序员的疏忽没有写,异常就会抛出到前台,很不好。

    另外,某些情况下异常是不能捕获的,比如需要事务处理的代码,捕获异常后会影响到事务回滚。

    所以,我们还是需要想办法用第2中方式来处理异常。n多年前曾经做过一个摩托罗拉的项目,其中一项需求就是对一个线上系统的异常做处理、不允许异常信息抛出到前台页面。当初那个项目并没有采用类似SpringMVC的框架,所以最终提出了用filter、在filter中拦截异常的处理方案并且被客户采纳。

    其实SpringMVC的统一异常处理方案和我上面项目中采用的方案非常类似。

    SpringMVC的异常处理

    Spring MVC提供了如下异常处理选项:

    1. @ExceptionHandle
    2. @ExceptionHandle + @ControllerAdvice
    3. 自定义异常处理器HandlerExceptionResolver
    @ExceptionHandle

    @ExceptionHandle可以作用在Controller中,比如,在上面发生异常的Controller中增加一个@ExceptionHandle注解的方法:

        @GetMapping(value="/hello",produces={"text/html; charset=UTF-8"})
        @ResponseBody
        public String hello(ModelAndView model){
            int c = 100 / 0;
            return "

    User info

    "; } @ExceptionHandler(Exception.class) @ResponseBody public String handle(Exception ex){ return "错了在helloworld Controller error msg is ==="; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行:
    在这里插入图片描述

    说明Hello方法中发生的异常,已经被handle方法处理,前台页面不再会出现异常信息。

    问题解决了!

    但是@ExceptionHandle只在当前Controller文件中生效,也就是说,当前Controller中的方法、或者方法调用的service层、dao层等发生的异常,才会被捕获到。其他Controller中发生的异常依然不会被捕获。

    这样的话,就需要对每一个Controller增加@ExceptionHandle进行处理,处理起来还是有点麻烦。

    统一异常处理

    @ExceptionHandle + @ControllerAdvice可以实现统一异常处理。

    @ControllerAdvice注解可以实现:

    On startup, RequestMappingHandlerMapping and ExceptionHandlerExceptionResolver detect controller advice beans and apply them at runtime. Global @ExceptionHandler methods, from an @ControllerAdvice, are applied after local ones, from the @Controller. By contrast, global @ModelAttribute and @InitBinder methods are applied before local ones.

    SpringMVC启动的过程中,RequestMappingHandlerMapping和ExceptionHandlerExceptionResolver会检测到advice bean并且在运行时会使用他们。在@ControllerAdvice中的@ExceptionHandler会变成一个全局的异常处理器、在本地异常处理器之后生效。并且,@ModelAttribute和 @InitBinder会在本地的之后生效。

    这段话的意思就是,@ExceptionHandle + @ControllerAdvice之后,@ExceptionHandle就会变成全局异常处理器。所谓的本地异常处理器,就是写在Controller中的@ExceptionHandle异常处理器。

    这个工作是ExceptionHandlerExceptionResolver干的,源码中可以看到:

    @Nullable
    	protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
    			@Nullable HandlerMethod handlerMethod, Exception exception) {
    
    		Class handlerType = null;
            //先找"local"异常处理器
    		if (handlerMethod != null) {
    			// Local exception handler methods on the controller class itself.
    			// To be invoked through the proxy, even in case of an interface-based proxy.
    			handlerType = handlerMethod.getBeanType();
    			ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
    			if (resolver == null) {
    				resolver = new ExceptionHandlerMethodResolver(handlerType);
    				this.exceptionHandlerCache.put(handlerType, resolver);
    			}
    			Method method = resolver.resolveMethod(exception);
    			if (method != null) {
    				return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
    			}
    			// For advice applicability check below (involving base packages, assignable types
    			// and annotation presence), use target class instead of interface-based proxy.
    			if (Proxy.isProxyClass(handlerType)) {
    				handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
    			}
    		}
            //再找advice的异常处理器
    		for (Map.Entry entry : this.exceptionHandlerAdviceCache.entrySet()) {
    			ControllerAdviceBean advice = entry.getKey();
    			if (advice.isApplicableToBeanType(handlerType)) {
    				ExceptionHandlerMethodResolver resolver = entry.getValue();
    				Method method = resolver.resolveMethod(exception);
    				if (method != null) {
    					return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
    				}
    			}
    		}
    
    		return null;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    验证一下。

    加一个MyGlobalExceptionController:

    @ControllerAdvice
    public class MyGlobalExceptionController {
        @ExceptionHandler(Exception.class)
        @ResponseBody
        public String handle(Exception ex){
            return return "错了MyGlobalExceptionController error msg is ===";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    先不去掉HelloWorldController中的异常处理器,运行hello:
    在这里插入图片描述

    生效的是HelloWorldController中的异常处理器。

    然后去掉HelloWorldController中的异常处理器:
    在这里插入图片描述

    写在MyGlobalExceptionController中的全局异常处理器生效!

    自定义异常处理器HandlerExceptionResolver

    实现接口HandlerExceptionResolver,或者扩展虚拟类AbstractHandlerMethodExceptionResolver(勉强可以考虑),定义自己的异常处理器。

    其实,非必要(没想到有什么必要性)不建议!

    上一篇 Spring MVC 九:Context层级(基于配置)

  • 相关阅读:
    c语言结构体、函数以及指针练习(简单通讯录)
    C++之异常
    第 3 章:GO 的接口和抽象 拓展篇 - CRUD 接口实现示例
    php内核基础说明
    多卡聚合路由器在近海通讯中的应用
    iOS15适配 UINavigationBar和UITabBar设置无效,变成黑色
    Redis持久化
    hadoop namenode -format报错显示:命令未找到
    ESP8266_Rtos3.0环境搭建
    【Jenkins+K8s】持续集成与交付 (二十):K8s集群通过Deployment方式部署安装Jenkins
  • 原文地址:https://blog.csdn.net/weixin_44612246/article/details/133364092