1. 概述
SpringMVC 提供的异常处理主要有两种方式:
- 一种是直接实现自己的HandlerExceptionResolver
- 一种是使用注解
通过注解的方式实现处理异常主要有以下两种方式:
- 1 @ControllerAdvice+@ExceptionHandler:配置对全局异常进行处理
- 2 @Controller + @ExceptionHandler:配置对当前所在Controller的异常进行处理
在SpringMVC中,处理异常类实际上是HandlerExceptionResolver子类。HandlerExceptionResolver处理所有controller类在执行过程中抛出的未被处理的异常。
本文演示如何使用以上多种处理异常的方式,最后演示以不同的方式将异常结果返回给调用者
- 返回 modelAndView
- 返回一个页面的地址
- 返回 JSON
- 返回 http 错误码
2. 演示工程mvc
所有代码都中mvc工程中
3.1. 基础类和JSP页面
辅助类
EmployeeEx:POJO类
ExceptionJSONInfo:封装返回的JSON对象
public class EmployeeEx {
private String name;
private int id;
…
}
public class ExceptionJSONInfo {
private String url;
private String message;
…
}
定义测试的异常类
EmployeeExNotFoundException
public class EmployeeExNotFoundException extends Exception {
private static final long serialVersionUID = -3332292346834265371L;
public EmployeeExNotFoundException(int id){
super("EmployeeNotFoundException with id="+id);
}
}
EmployeeExJsonException
public class EmployeeExJsonException extends Exception {
private static final long serialVersionUID = -3332292346834265371L;
public EmployeeExJsonException(int id){
super("EmployeeExJsonException with id="+id);
}
}
JSP页面
本demo使用的jsp都在此“META-INF.resources.WEB-INF.page.exceptionhandling”目录下,由于代码简单,这里一一列出。
3. 通过注解的方式捕获异常
本节演示两种通过注解捕获异常的方式
- @Controller + @ExceptionHandler
- @ControllerAdvice + @ExceptionHandler
3.1. @Controller + @ExceptionHandler
@Controller:注解此类是Controller类 @ExceptionHandler:此注解注解到类的方法上,当此注解里定义的异常抛出时,此方法会被执行。如果@ExceptionHandler所在的类是@Controller,则此方法只作用在此类。如果@ExceptionHandler所在的类是@ControllerAdvice,则此方法会作用在全局。本节演示前种用法
下面演示这种用法:
EmployeeExController:
- 在@Controller里定义的@ExceptionHandler只截获所在类抛出的异常
- handleEmployeeNotFoundException()通过@ExceptionHandler定义要捕获的异常是EmployeeExNotFoundException,此方法会转到exceptionhandling/error页面,此页面会打印错误信息
@Controller
public class EmployeeExController {
private static final Logger logger = LoggerFactory.getLogger(EmployeeExController.class);
@RequestMapping(value="/emp/{id}", method=RequestMethod.GET)
public String getEmployee(@PathVariable("id") int id, Model model) throws Exception{
if(id==1){
throw new EmployeeExNotFoundException(id);
}else if(id==2){
…
}
/**
* 此方法只处理本类抛出的 EmployeeNotFoundException 异常
* @param request
* @param ex
* @return
*/
@ExceptionHandler(EmployeeExNotFoundException.class)
public ModelAndView handleEmployeeNotFoundException(HttpServletRequest request, Exception ex){
logger.error("Requested URL="+request.getRequestURL());
logger.error("Exception Raised="+ex);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", ex);
modelAndView.addObject("url", request.getRequestURL());
// 转到error.jsp
modelAndView.setViewName("exceptionhandling/error");
return modelAndView;
}
}
测试
执行URL:
http://127.0.0.1:8080/emp/1
输出

3.2. @ControllerAdvice + @ExceptionHandler
@ControllerAdvice:定义全局异常处理的类,默认情况下它会监控所有的@RequestMapping方法抛出的异常。不过我们也可以对指定过滤的条件
EmployeeExController
此方法中抛出SQLException
@Controller
public class EmployeeExController {
@RequestMapping(value="/emp/{id}", method=RequestMethod.GET)
public String getEmployee(@PathVariable("id") int id, Model model) throws Exception{
if(id==1){
….
}else if(id==2){
throw new SQLException("SQLException, id="+id);
}else if(id==3){
…
}
GlobalExceptionHandler
@ControllerAdvice:通过@ExceptionHandler方法捕获特定异常。默认情况下@ControllerAdvice监控所有的@RequestMapping方法,也可以对指定过滤的条件。下面的方法会捕获所有抛出SQLException的@RequestMapping方法
/**
* 全局异常处理异常
* @ControllerAdvice
*
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(SQLException.class)
public String handleSQLException(HttpServletRequest request, Exception ex) {
logger.info("SQLException Occured:: URL=" + request.getRequestURL());
return "exceptionhandling/database_error";
}
….
}
@ControllerAdvice默认监控所有的@RequestMapping方法,也可以对指定过滤的条件:
// 监控所有的被@RestController注解的Controllers类
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}
// 监控特定的包下的Controllers类
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}
// 监控指定类的Controllers类
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}
测试
执行URL:
http://127.0.0.1:8080/emp/2
输出

4. 通过HandlerExceptionResolver的方式捕获
在接口HandlerExceptionResolver,我们可以自定义实现全局异常捕获
4.1. 系统默认实现的HandlerExceptionResolver
以下是系统默认加载到spring mvc容器中的HandlerExceptionResolver
- ExceptionHandlerExceptionResolver: 根据@ExceptionHandler注解的方法处理对应的异常。其实上文的通过注解的方式处理异常,实际就是在这个类中实现
- ResponseStatusExceptionResolver: 根据@ResponseStatus注解的方法处理异常
- DefaultHandlerExceptionResolver: 将异常转化为特定的HTTP的状态码
- HandlerExceptionResolverComposite:此类通过列表包含以上3个HandlerExceptionResolver,当捕获异常时,会循环调用以上3个HandlerExceptionResolver进行处理
4.2. 实现自己的HandlerExceptionResolver
此类只演示HandlerExceptionResolver的用法,只打印错误信息,不进行任何处理。通过接口Ordered的getOrder定义此HandlerExceptionResolver的优先级,优先级最高
@Component // 需要带上此注解
public class MySimpleHandlerExceptionResolverimplements HandlerExceptionResolver,Ordered{
private static final Logger logger = LoggerFactory.getLogger(MySimpleMappingExceptionResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
logger.info("url = {}, exception message = {}", request.getRequestURI(), ex.getMessage());
// 返回null,让后面HandlerExceptionResolver继续进行处理;如果不让后面的HandlerExceptionResolver进行处理,则这里返回一个ModelAndView对象即可
return null;
}
public int getOrder(){
// 表示此HandlerExceptionResolver的优先级最高
return Ordered.HIGHEST_PRECEDENCE;
}
}
4.3. 测试
由于此andlerExceptionResolver的优先级最高,所以所有抛出异常被会被此类捕获,如:请求 http://127.0.0.1:8080/emp/2
后台有打印信息,说明此配置生效了
2017-12-20 11:39:03.145 [http-nio-8080-exec-1] INFO c.h.s.m.e.MySimpleHandlerExceptionResolver - url = /emp/2, exception message = SQLException, id=2
5. 对异常进行处理
捕获异常后,我们可以返回所有的spring mvc能够返回的所有的类型,这里只演示以下几种:
- 返回 modelAndView
- 返回一个页面的地址
- 返回 JSON
- 返回 http 错误码
5.1. 返回 modelAndView
上面的 “@Controller + @ExceptionHandler” 演示这个方式的用法
5.2. 返回一个页面的地址
上面的 “@ControllerAdvice + @ExceptionHandler” 演示这个方式的用法
5.3. 返回 JSON
EmployeeExController
@Controller
public class EmployeeExController {
@RequestMapping(value = "/emp/{id}", method = RequestMethod.GET)
public String getEmployee(@PathVariable("id") int id, Model model) throws Exception {
..
} else if (id == 4) {
throw new EmployeeExJsonException(id);
} else if (id == 10) {
…
}
GlobalExceptionHandler
@ResponseBody:截获异常,生成对象并通过此注解生成json,返回给客户端
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EmployeeExJsonException.class)
public @ResponseBody ExceptionJSONInfo handleEmployeeNotFoundException(HttpServletRequest request, Exception ex) {
ExceptionJSONInfo response = new ExceptionJSONInfo();
response.setUrl(request.getRequestURL().toString());
response.setMessage(ex.getMessage());
return response;
}
}
测试
执行 http://127.0.0.1:8080/emp/4
返回

5.4. 返回 http 错误码
如果发生IOException异常,则作为返回http错误代码为404。
EmployeeExController
@Controller
public class EmployeeExController {
@RequestMapping(value="/emp/{id}", method=RequestMethod.GET)
public String getEmployee(@PathVariable("id") int id, Model model) throws Exception{
…
}else if(id==3){
throw new IOException("IOException, id="+id);
}else if(id==10){
…
}
}
GlobalExceptionHandler :
如果发生IOException异常,则作为返回http错误代码为404
这里写代码片
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* For IOException, we are returning void with status code as 404,
* so our error-page will be used in this case.
*
*/
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "IOException occured")
@ExceptionHandler(IOException.class)
public void handleIOException() {
logger.error("IOException handler executed");
// returning 404 error code
}
}
测试
执行 http://127.0.0.1:8080/emp/3
6. 代码
上文的详细代码见github代码,请尽量使用tag v0.5,不要使用master,因为master一直在变,不能保证文章中代码和github上的代码一直相同