Controller层的异常统一处理及返回

一、为什么要做这件事?

不知道你平时在写Controller层接口的时候,有没有注意过抛出异常该怎么处理,是否第一反应是想着用个try-catch来捕获异常?

但是这样地处理只适合那种编译器主动提示的检查时异常,因为你不用try-catch就过不了编译检查,所以你能主动地抓获异常并进行处理。但是,如果存在运行时异常且你没有来得及想到去处理它的时候会发生什么呢?我们可以来先看看下面的这个没有处理运行时异常的例子:

@RestController

public class ExceptionRest {

@GetMapping("getNullPointerException")

public Map getNullPointerException(){

throw new NullPointerException("出现了空指针异常");

}

}

以上代码在基于maven的SpringMVC项目中,使用tomcat启动后,浏览器端发起如下请求:

http://localhost:8080/zxtest/getNullPointerException

访问后得到的结果是这样的:

浏览器收到的报错信息

可以看到,我们在Controller接口层抛出了一个空指针异常,然后没有捕获,结果异常堆栈就会返回给前端浏览器,给用户造成了非常不好的体验。

除此之外,前端从报错信息中能看到后台系统使用的服务器及中间件类型、所采用的框架信息及类信息,甚至如果后端抛出的是SQL异常,那么还可以看到SQL异常的具体查询的参数信息,这是一个中危安全漏洞,是必须要修复的。

PS:上面的项目如果使用SpringBoot的话可能在前端得不到报错信息,因为SpringBoot自动对返回的报错内容做了处理,我们需要使用Maven的web模板创建一个只包含SpringMVC的项目来复现以上场景。

二、如何做到统一处理?

当出现这种运行时异常的时候,我们想到的最简单的方法也许就是给可能会抛出异常的代码加上异常处理,如下所示:

@RestController

public class ExceptionRest {

private Logger log = LoggerFactory.getLogger(ExceptionRest.class);

@GetMapping("getNullPointerException")

public Map getNullPointerException(){

Map returnMap = new HashMap();

try{

throw new NullPointerException("出现了空指针异常");

}catch(NullPointerException e){

log.error("出现了空指针异常",e);

returnMap.put("success",false);

returnMap.put("mesg","请求发生异常,请稍后再试");

}

return returnMap;

}

}

因为我们手动地在抛出异常的地方加上了处理,并妥善地返回发生异常时该返回给前端的内容,因此,当我们再次在浏览器发起相同的请求时得到就是以下内容:

{

success: false,

mesg: "请求发生异常,请稍后再试"

}

貌似问题得到了解决,但是你能确保你可以在所有可能会发生异常的地方都正好捕获了异常并处理吗?你能确保团队的其他人也这么做?

很明显,你需要一个统一的异常捕获与处理方案。

2.1 使用HandlerExceptionResolver

HandlerExceptionResolver是一个异常处理接口,实现它的类在spring配置文件中注册后就能捕获Controller层抛出的所有异常,我们就是基于此来实现统一Web异常的处理和返回结果的配置。

2.1.1 基本使用

方式一:使用HttpServletResponse返回JSON信息

@Controller

public class WebExceptionResolver implements HandlerExceptionResolver {

private Logger log = LoggerFactory.getLogger(WebExceptionResolver.class);

@Override

public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

log.error("请求{}发生异常!",httpServletRequest.getRequestURI());

ModelAndView mv = new ModelAndView();

httpServletResponse.setContentType("application/json;charset=UTF-8");

httpServletResponse.setCharacterEncoding("UTF-8");

String retStr = "{\"success\":false,\"msg\":\"请求异常,请稍后再试\"}";

try{

httpServletResponse.getWriter().write(retStr);

}catch(Exception ex){

log.error("WebExceptionResolver处理异常",ex);

}

// 需要返回空的ModelAndView以阻止异常继续被其它处理器捕获

return mv;

}

}

通过以上的处理,所有Web层的异常都能被WebExceptionResolver捕获并在resolveException中进行处理,然后可以使用HttpServletResponse来统一返回想返回的信息。如下是请求相同的链接时返回给浏览器的内容:

拦截Web异常后的返回信息

方式二:使用ModelAndView返回JSON信息

@Controller

public class WebExceptionResolver implements HandlerExceptionResolver {

private Logger log = LoggerFactory.getLogger(WebExceptionResolver.class);

@Override

public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

log.error("请求{}发生异常!",httpServletRequest.getRequestURI());

ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());

mv.addObject("success","false");

mv.addObject("mesg","请求异常,请稍后再试");

return mv;

}

}

这两种方式效果等同,唯一的区别是,使用第二种方式需要多引入jackson-databind包。

com.fasterxml.jackson.core

jackson-databind

2.1.2 具体异常的处理

有时候我们需要在发生特定异常的时候做一些处理,那么只需要判断捕获的异常类型进行分别处理即可:

@Controller

public class WebExceptionResolver implements HandlerExceptionResolver {

private Logger log = LoggerFactory.getLogger(WebExceptionResolver.class);

@Override

public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

log.error("请求{}发生异常!",httpServletRequest.getRequestURI());

ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());

mv.addObject("success","false");

if(e instanceof NullPointerException){

mv.addObject("mesg","请求发生了空指针异常,请稍后再试");

}else if(e instanceof ClassCastException){

mv.addObject("mesg","请求发生了类型转换异常,请稍后再试");

}else{

mv.addObject("mesg","请求发生异常,请稍后再试");

}

return mv;

}

}

2.1.3 异常处理链

如果存在多个实现HandlerExceptionResolver的异常处理类,那么它们就会形成一个处理链,此时需要在spring配置文件中声明哪个处理在前,哪个处理在后,越是在前面声明的处理类就越是可以先对异常处理,甚至可以拦截异常,不再被后续的处理器处理。

PS:如果打算在当前的异常处理器中拦截异常,防止继续往外抛出被别的处理器处理,那么直接在最后返回一个空的ModelAndView对象即可。如果打算不拦截这个异常,继续让别的处理器处理的话,就返回null即可。

// 需要返回空的ModelAndView以阻止异常继续被其它处理器捕获

return mv;

// 返回null将不会拦截异常,其它处理器可以继续处理该异常

return null;

2.2 使用@ExceptionHandler

Spring3.2以后,SpringMVC引入了ExceptionHandler的处理方法,使得对异常的处理变得更加简单和精确,你唯一需要做的就是新建一个Controller,然后再里面加上两个注解即可完成Controller层所有异常的捕获与处理。

2.2.1基本使用

新建一个Controller如下:

@ControllerAdvice

public class ExceptionConfigController {

@ExceptionHandler

public ModelAndView exceptionHandler(Exception e){

ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());

mv.addObject("success",false);

mv.addObject("mesg","请求发生了异常,请稍后再试");

return mv;

}

}

我们在如上的代码中,类上加了@ControllerAdvice注解,表示它是一个增强版的controller,然后在里面创建了一个返回ModelAndView对象的exceptionHandler方法,其上加上@ExceptionHandler注解,表示这是一个异常处理方法,然后在方法里面写上具体的异常处理及返回参数逻辑即可,如此就完成了所有的工作,真的是太方便了。

我们在浏览器发起调用后就返回了如下的结果:

{

success: false,

mesg: "请求发生了异常,请稍后再试"

}

2.2 具体异常的处理

相比与HandlerExceptionResolver而言,使用@ExceptionHandler更能灵活地对不同的异常进行分别的处理。并且,当抛出的异常是指定异常的子类,那么照样能够被捕获和处理。

我们改变下controller层的代码如下:

@RestController

public class ExceptionController {

@GetMapping("getNullPointerException")

public Map getNullPointerException() {

throw new NullPointerException("出现了空指针异常");

}

@GetMapping("getClassCastException")

public Map getClassCastException() {

throw new ClassCastException("出现了类型转换异常");

}

@GetMapping("getIOException")

public Map getIOException() throws IOException {

throw new IOException("出现了IO异常");

}

}

已知NullPointerException和ClassCastException都继承RuntimeException,而RuntimeException和IOException都继承Exception。

我们在ExceptionConfigController做这样的处理:

@ControllerAdvice

public class ExceptionConfigController {

// 专门用来捕获和处理Controller层的空指针异常

@ExceptionHandler(NullPointerException.class)

public ModelAndView nullPointerExceptionHandler(NullPointerException e){

ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());

mv.addObject("success",false);

mv.addObject("mesg","请求发生了空指针异常,请稍后再试");

return mv;

}

// 专门用来捕获和处理Controller层的运行时异常

@ExceptionHandler(RuntimeException.class)

public ModelAndView runtimeExceptionHandler(RuntimeException e){

ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());

mv.addObject("success",false);

mv.addObject("mesg","请求发生了运行时异常,请稍后再试");

return mv;

}

// 专门用来捕获和处理Controller层的异常

@ExceptionHandler(Exception.class)

public ModelAndView exceptionHandler(Exception e){

ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());

mv.addObject("success",false);

mv.addObject("mesg","请求发生了异常,请稍后再试");

return mv;

}

}

那么

当我们在Controller层抛出NullPointerException时,就会被nullPointerExceptionHandler进行处理,然后拦截。

{

success: false,

mesg: "请求发生了空指针异常,请稍后再试"

}

当我们在Controller层抛出ClassCastException时,就会被runtimeExceptionHandler进行处理,然后拦截。

{

success: false,

mesg: "请求发生了运行时异常,请稍后再试"

}

当我们在Controller层抛出IOException时,就会被exceptionHandler进行处理,然后拦截。

{

success: false,

mesg: "请求发生了异常,请稍后再试"

}

三、总结

SpringMVC为我们提供的Controller层异常处理真的是太方便了,尤其是@ExceptionHandler,推荐大家使用。

本文完。

java异常统一处理,Controller层的异常统一处理及返回相关推荐

  1. 【统一异常处理】@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

    1.利用springmvc注解对Controller层异常全局处理 对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service ...

  2. @ControllerAdvice + @ExceptionHandler 处理 全部Controller层异常

    对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚. 如此一来, ...

  3. controller层要写什么_别再写满屏的try-catch了,真丑,全局异常处理不会吗?

    本文讲得比较细,所以篇幅较长.请认真读完,希望读完后能对统一异常处理有一个清晰的认识. 背景 软件开发过程中,不可避免的是需要处理各种异常,就我自己来说,至少有一半以上的时间都是在处理各种异常情况,所 ...

  4. 基于 mockMvc 的 Controller 层单元测试

    场景 单元测试是个让人很纠结的东东,dealline 催的很紧,不想写单测,但当项目复杂到一定程度需要重构时,返现没有单测,不敢随便改代码,生怕"敲一锤子,倒一大片",但此时再补, ...

  5. Spring - @ControllerAdvice + @ExceptionHandler全局处理Controller层异常(转)

    Spring - @ControllerAdvice + @ExceptionHandler全局处理Controller层异常(转) 参考文章: (1)Spring - @ControllerAdvi ...

  6. @ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

    @ControllerAdvice 和 @ExceptionHandler 的区别 ExceptionHandler, 方法注解, 作用于 Controller 级别. ExceptionHandle ...

  7. Spring中Controller层、Filter层、Interceptor层全局统一异常处理

    Controller层.Filter层.Interceptor层全局统一异常处理 SpringBoot为异常处理提供了很多优秀的方法,但是像我这种新手在处理异常时还是会觉得一头包,终于我痛定思痛,总结 ...

  8. java方法被编译器调用_我异常了,快来捕获我,Java异常简述

    在我们日常编程中,异常处理是必不可少的,异常处理是否得当关系到程序的健壮性和后续维护成本. 试想一下,如果一个项目从头到尾没有考虑过异常处理,当程序出错从哪里寻找出错的根源?但是如果一个项目异常处理设 ...

  9. 编写高质量代码改善java程序的151个建议——[110-117]异常及Web项目中异常处理

    编写高质量代码改善java程序的151个建议--[110-117]异常及Web项目中异常处理 原创地址:http://www.cnblogs.com/Alandre/(泥沙砖瓦浆木匠),需要转载的,保 ...

最新文章

  1. linux mysql详解,Linux 下mysql安装使用详解
  2. 交流潮流matlab程序,大神们,求个电力系统潮流计算的matlab程序。
  3. 在sublime-text中设置浏览器预览
  4. JS模拟模式窗口效果
  5. 使用ConfigModule.withConfig替换SAP Spartacus标准Component
  6. java互联网架构师入门进阶之路
  7. PS发光眩光效果插件:BBTools Glow Glare for Mac
  8. GCC4.8对new和delete的参数匹配新要求
  9. BIO,NIO和AIO的区别
  10. vue三种常用获取input值方法
  11. 在AJAX中可以使用的Response.Redirect 的冲突解决办法
  12. 委托与Lambda表达式
  13. qpython 3h下载_【分享】QPython 3H3.0.0 一个伟大的脚本编辑器!
  14. 计算机老出现安全警报怎么办,windows安全警报怎么关闭,教您怎么关闭windows安全警报...
  15. Java并发常见面试题(二)
  16. Postman 都有女朋友了,我特么竟然还单身
  17. 程序员缺少自信怎么办? AI 训练数千次的回答
  18. 最新十大域名注册商.com域名注册量排行榜
  19. 华硕飞行堡垒 新机子安装双系统 linux
  20. 【C语言】汉诺塔问题(图文详解)

热门文章

  1. javaweb----DAO模型设计
  2. 【Vue】Vue入门 -(本地篇+网络篇)代码示例及运行效果
  3. netty系列之:channelPipeline详解
  4. 区块链系列教程之:比特币的世界
  5. 全局中断_实时性迷思(3)——80%时间屏蔽了中断,实时性还有救么?
  6. Effective Java之努力使失败保持原子性(六十四)
  7. 【最新合集】编译原理习题(含答案)_8-10语法制导翻译_MOOC慕课 哈工大陈鄞
  8. (干货满满!)session和cookie作用原理,区别(史上最详细)
  9. 28行代码AC——习题3-12 浮点数(UVA 11809 - Floating-Point Numbers)——解题报告
  10. 为什么将老年代移动到方法区