摘要
介绍spring mvc控制器中统一处理异常的两种方式:HandlerExceptionResolver以及@ExceptionHandler;以及使用@ControllerAdvice将@ExceptionHandler方法的影响扩大。

一、问题的提出
Spring MVC 项目的开发中,不管是底层的数据库操作过程,业务层的业务逻辑的处理,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常。

这些异常可以在每个单独的环节捕获,处理;但是大多数情况下,异常情况都会反馈到控制器(无论是通过抛出异常的方式,还是自定义特殊返回值,如null等的方式),然后由控制器结合具体异常情况,返回特定信息(通常是不同的返回码,错误信息)给http请求的调用方。

然而,每个环节都单独捕获处理异常,业务代码可读性不强,工作量大且不好统一,维护的工作量也很大。那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。

二、统一异常处理
对于spring mvc来说,一次http请求在服务端处理涉及到的环节一般如下:

每个环节都有可能发生异常;问题的解决思路,恰恰是对于异常处理的自然过程: 能够处理异常就捕获处理,不能处理异常就将异常抛出(或者转换抛出)。

一般来说,服务层和持久层发生的异常,这两层都无能为力,因为这些异常情况会转换为相关的信息返回到http调用方。既然不能处理,何不直接抛出(转换抛出)到控制层?然后由http请求的入口处——控制层统一处理。

那么,可能的处理方法是这样的:

controller:
@RequestMapping(...)
public Object doController(){
try {
invokeService();
} catch(CustomizedEx1 e) {
// 返回码1
} catch(CustomizedEx2 e) {
// 返回吗2
} ...
catch(Exception e) {
// 系统异常 ?
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
这样可以做到在一次请求中,统一在入口控制器方法处处理异常。但是这样的话,对于每个请求,在控制器中处理将请求处理委托给服务层的代码外,不得不书写捕获各种异常的catch块,对于懒惰的程序员来说,无疑是灾难性的操作。

封装

考虑一下异常的种类,事实上业务异常的种类是有限的,不同的请求出现的异常情况无非就那么几种。这时可将catch处理封装起来,作为一个统一的方法,共各个controller方法调用。

superController:
class SuperController {
public Object uniformExHandle(Exception e) {
if (e instanceof CustomizedEx1) {
// 返回码1
} else if (e instanceof CustomizedEx2) {
// 返回码2
}...
else {
// 系统异常 ?
}
}
}

specificContoller:
@Controller
class HelloController extends SuperController {
@RequestMapping(...)
public Object doController(){
try {
invokeService();
}
catch(Exception e) {
uniformExHandle(e);
}
}
}
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
封装异常处理,为了各个控制器能够方便调用,抽象一个控制器的父类,供各个具体控制器继承。

松耦合

上面的封装+控制器统一异常处理,似乎解决了开始提出的问题,事实上也解决了问题。但是也引入了新的问题:所有控制器不得不继承 SuperController 以获得统一处理异常的能力。

这是一种紧耦合的体现,彷佛回到了EJB时代,为了获取框架的功能,一个类必须实现一堆类,继承一堆接口。这也是良好的设计提倡 少用继承,多用组合 的原因。

诚然,使用组合的方式,将异常统一处理暴露出去供控制器方法调用,是一种松耦合的方法。但是既然在spring mvc的生态中,spring mvc也考虑到了这个问题,提供了两种方式实现控制器的异常处理:

使用实现HandlerExceptionResolver接口的类处理异常
使用@Exception注解的方法处理异常
这两种方法原理一样,区别只在使用方式而已。

三、HandlerExceptionResolver
参考HandlerExceptionResolver的jdk文档,就能轻松了解如何使用。

public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}
1
2
3
4
5
6
实现HandlerExceptionResolver接口的类能够解决在处理器映射或处理器方法执行过程中产生的异常,通常导向错误的view,实现类通常需要注册到应用spring上下文中才能生效;其resolveException方法试图解决在处理器执行期间抛出的异常,并在适当的情况下的返回代表特定错误页面的ModelAndView。返回的ModelAndView为空时标明异常已经被成功地解决,但是没有错误页面返回,例如,设置了错误码。

简单的使用方式:

@Component // 必须注册到spring容器中才有效
public class GlobalExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
String exMsg = "";
if(null != ex) {
exMsg = ex.getMessage();
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("exception");
Map<String, String> map = new HashMap<String, String>();
map.put( "key", "exception occured: " + exMsg);
modelAndView.addAllObjects(map);
return modelAndView;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
原理

DispatcherServlet是SpringMVC的核心,当然他也负责了这个“全局异常的处理”。

1)分发请求中捕获异常:

doDispatch()是DispatcherServlet分发请求的入口,方法中捕获请求执行可能的异常,并交给processDispatchResult()处理

DispatcherServlet#doDispatch():
try {
...
// Actually invoke the handler.
v = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
} catch (Exception ex) {
dispatchException = ex;
}
// 处理结果以及异常
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
1
2
3
4
5
6
7
8
9
10
11
2)processDispatchResult核心

DispatcherServlet#processDispatchResult():
if (exception != null) { // 处理结果,存在异常
if (exception instanceof ModelAndViewDefiningException) {
...
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 调用异常处理获得 ModelAndView
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}

DispatcherServlet#processHandlerException():
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
可见最终遍历了DispatcherServlet的handlerExceptionResolvers,依次调用配置的exception resolver来处理异常,直到异常处理器返回的ModelAndView不为空。

3)handlerExceptionResolvers初始化

在初始化阶段,会初始化异常处理器,将spring容器中注册的HandlerExceptionResolver加入到DispatcherServlet的handlerExceptionResolvers列表中:

@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
...
initHandlerExceptionResolvers(context);
...
}
private void initHandlerExceptionResolvers(ApplicationContext context) {
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
四、ExceptionHandler
上面的HandlerExceptionResolver方式也需要实现这个接口;另一种注解方式是使用@ExceptionHandler,只需在指定的控制器中简单使用即可:

@Controller
class ExampleController {
@ExceptionHandler(Exception.class)
@ReponseBody
public Object exceptionHandler(Exception e) {
...
return new Object();
}

@RequestMapping(...)
public Object doController() {
}
1
2
3
4
5
6
7
8
9
10
11
12
**!!!**需要注意的是,注解@ExceptionHandler修饰的方法,只能处理所在控制器的@RequestMapping方法的未捕获异常,超出该控制器,或者没有使用@RequestMapping修饰的方法调用,发生的未捕获异常都不会被处理。

@ExceptionHandler

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {

/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};

}
1
2
3
4
5
6
7
8
9
10
11
12
简要说来,使用这个注解标注的方法能处理方法所在Controller的处理器中未捕获的异常,处理异常的方法可以有多种方式的签名,参数可以有

异常类型的参数;Exception或者特定类型的异常类,要与value中指定的异常匹配
request和response对象;javax.servlet.ServletRequest/javax.servlet.ServletResponse,javax.servlet.http.HttpServletRequest/javax.servlet.http.HttpServletRequest
Session对象
等等
返回值可以是:

ModelAndView, model object, Map, View
表示视图名的String
@Response修饰,设置响应内容;使用配置的message converts将返回值转换为响应流
HttpEntity / ResponseEntity,同样使用message converts转换
void,如果方法自己处理http response输出
可以看出@ExceptionHandler方式灵活得多,而且其原理与HandlerExceptionResolver是一样的。

全局配置

由于@ExceptionHandler方法只能处理同一个控制器内的方法,这样每一个控制器都要声明@ExceptionHandler方法?

很自然的可以想到在所有控制器的一个父类中声明一个@ExceptionHandler方法,即可全局处理。更优雅的方式是使用@ControllerAdvice;

正如其名字一样,注解修饰的类是“协助”其他控制器,是@Component的具化注解,通过类路径扫描(component scan)修饰的类可以被自动检测(注册到spring容器)。
典型的用法是用来定义 @ExceptionHandler, @InitBinder, 和 @ModelAttribute方法,这些方法可运用于所有的@RequestMapping方法。默认情况下@ControllerAdvice的修饰类会“协助”所有已知的控制器。

以下为使用demo:

@ControllerAdvice
public class UniformControllerExHandler {
@ExceptionHandler(Throwable.class)
@ResponseBody
public Object exHandler(Throwable e) {
AgentBaseResponse resp = new AgentBaseResponse();
resp.setRetMsg(e.getMessage());
log.error("控制器异常(Throwable), 返回: " + JSON.toJSONString(resp), e);
return resp;
}
}
1
2
3
4
5
6
7
8
9
10
11
五、总结
spring mvc中业务方法的异常,可以在控制层统一处理。
通过实现spring提供的HandlerExceptionResolver接口,并把实现类注入到spring容器,可统一处理控制器方法未捕获的异常。
另一种方法是使用@ExceptionHandler,借助@ControllerAdvice可将影响扩大到每一个控制器。

SpringMVC 控制器统一异常处理的更多相关文章

  1. 【SpringMVC】统一异常处理

    一.需求 二.统一异常处理解决方案 2.1 定义异常 2.2 异常处理 2.3 配置统一异常处理器 2.4 异常处理逻辑 一.需求 一般项目中都需要作异常处理,基于系统架构的设计考虑,使用统一的异常处 ...

  2. 【SpringMVC学习07】SpringMVC中的统一异常处理

    我们知道,系统中异常包括:编译时异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试通过手段减少运行时异常的发生.在开发中,不管是dao层 ...

  3. SpringMVC框架08——统一异常处理

    前言 在Spring MVC 应用的开发中,不管是对底层数据库操作,还是业务层或控制层操作,都会不可避免地遇到各种可预知的.不可预知的异常需要处理.如果每个过程都单独处理异常,那么系统的代码耦合度高, ...

  4. Java springmvc 统一异常处理的方案

    前言:为什么要统一异常处理?经常在项目中需要统一处理异常,将异常封装转给前端.也有时需要在项目中统一处理异常后,记录异常日志,做一下统一处理. Springmvc 异常统一处理的方式有三种. 一.使用 ...

  5. springMVC统一异常处理

    Spring MVC处理异常有3种方式: 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver: 实现Spring的异常处理接口HandlerExc ...

  6. Java生鲜电商平台-统一异常处理及架构实战

    Java生鲜电商平台-统一异常处理及架构实战 补充说明:本文讲得比较细,所以篇幅较长. 请认真读完,希望读完后能对统一异常处理有一个清晰的认识. 背景 软件开发过程中,不可避免的是需要处理各种异常,就 ...

  7. 使用Spring MVC统一异常处理实战

    1 描述 在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的.不可预知的异常需要处理.每个过程都单独处理异常,系统的代码耦合 ...

  8. SpringCloud学习之Zuul统一异常处理及回退

    一.Filter中统一异常处理 其实在SpringCloud的Edgware SR2版本中对于ZuulFilter中的错误有统一的处理,但是在实际开发当中对于错误的响应方式,我想每个团队都有自己的处理 ...

  9. SpringBoot 统一异常处理

    统一异常处理: @ControllerAdvice public class GlobalExceptionHandler { private Logger logger = LoggerFactor ...

随机推荐

  1. USACO44 TimeTravel 时间旅行(链表)

    第一眼看到这题,woc,这不是主席树!?旁边HZ也表示同意,然后cGh队长就慢悠悠的过来:“想什么,USACO会有主席树!?” ↓打脸不解释,大家可以去%ta的博客(这样ta就不会D飞我了~)http ...

  2. Oracle 删除用户和表空间////Oracle创建删除用户、角色、表空间、导入导出、...命令总结/////Oracle数据库创建表空间及为用户指定表空间

    Oracle 使用时间长了, 新增了许多user 和tablespace. 需要清理一下 对于单个user和tablespace 来说, 可以使用如下命令来完成. 步骤一:  删除user drop ...

  3. 《Visual C++ 2010入门教程》系列二:安装、配置和首次使用VS2010

    作者:董波 日期:2010.6.15 写在前面 在我还在上学的时候,我选择了C++,最初我用VC6作为我的IDE,我看过很多本C++的教材,有的适合我,有的不适合我,其中有一本叫<Visual ...

  4. zoj 3865

    Superbot Time Limit: 2 Seconds      Memory Limit: 65536 KB Superbot is an interesting game which you ...

  5. 2-18 matplotlib模块的使用

    import numpy as np import matplotlib.pyplot as plt x = np.array([1,2,3,4,5,6,7,8]) y = np.array([3,5 ...

  6. 转载别人的ognl

    一.循环遍历集合 1.在jsp中引入标准函数声明<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix=" ...

  7. idea打开可选项目

    配置上面的即可

  8. Pycharm的安装教学

    Python环境搭建—安利Python小白的Python和Pycharm安装详细教程 人生苦短,我用Python.众所周知,Python目前越来越火,学习Python的小伙伴也越来越多.最近看到群里的 ...

  9. Bootstrap简介及安装使用

    Bootstrap 简介 什么是 Bootstrap? Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的 ...

  10. QString:常用成员函数总结

    QString是Qt中使用频率最高的几种数据类型之一,主要在于其提供了大量功能强大的成员函数,这里重点介绍一些常用的成员函数: 一.字符串处理相关 1.1 split() (拆分字符串) split( ...