异常处理
在项目中如何处理出现的异常,在每个可能出现异常的地方都写代码捕捉异常?这显然是不合理的,当项目越来越大是也是不可维护的。那么如何保证我们处理异常的代码精简且便于维护呢?这就是本篇要讲的内容—>异常处理。
在Spring MVC中我们可以通过以下2中途径来对异常进行集中处理:
一.继承HandlerExceptionResolver接口实现自己的处理方法
public class MyHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //添加自己的异常处理逻辑,如日志记录等 // TODO Auto-generated method stub return new ModelAndView("exception"); } }
然后在项目的配置文件中添加:
<bean id="exceptionResolver" class="所在包名.MyHandlerExceptionResolver"/>
这样就完成了异常的捕捉和处理。
二.借助@ExceptionHandler注解来实现零配置的异常捕捉和处理
我们介绍了第一种捕捉处理异常方式,但是第一种方式需要在配置文件中进行配置,有的时候我们会觉得配置文件内容太多太乱,那么我们就可以借助@ExceptionHandler注解来实现零配置的异常捕捉和处理。
首先,在我们项目的包com.demo.web.controllers中为controller建立一个父类BaseController,内容如下:
package com.demo.web.controllers; import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.ExceptionHandler; public abstract class BaseController { @ExceptionHandler public String exception(HttpServletRequest request, Exception e) { //添加自己的异常处理逻辑,如日志记录 request.setAttribute("exceptionMessage", e.getMessage()); // 根据不同的异常类型进行不同处理 if(e instanceof SQLException) return "testerror"; else return "error"; } }
其次,修改项目中HelloWorldController让它继承于BaseController以便进行测试:
public class HelloWorldController extends BaseController{ //...内容省略 }
然后,修改HelloWorldController 中的index方法,使其抛出异常,看能不能正常捕捉:
//@AuthPassport @RequestMapping(value={"/index","/hello"}) public ModelAndView index() throws SQLException{ throw new SQLException("数据库异常。"); /*ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("message", "Hello World!"); modelAndView.setViewName("index"); return modelAndView;*/ }
最后,在views文件夹中添加testerror.jsp视图来显示错误信息:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>error!</title> </head> <body> ${exceptionMessage} </body> </html>
运行项目:
可以看到异常已经被捕捉并显示,这样只要把我们的其它的Controller全部继承于BaseController就能实现异常的集中捕捉和处理了。
代码下载:http://pan.baidu.com/s/1c0eTseG
注: 之前没注意前11篇的示例代码,不知道为什么当时打包上传上去的是没有.project项目文件的,导致下载后不能直接导入eclipse运行,虚拟机又 被我删掉了,这些示例代码也没有备份,但是代码文件还在的,所以可以新建一个Dynamic Web Project把对应的配置文件和controller还有view导入就可以了,给大家造成的不便说声抱歉。
异步调度和事件机制的异常处理
Spring 4.1对异步调用提供了AsyncResult及SuccessCallback、FailureCallback、和异常处理的支持;对事件调度也提供了相应的异常处理器。
1、事件调度异常处理器
1.1、定义异常处理器:
- public class MyErrorHandler implements ErrorHandler {
- @Override
- public void handleError(Throwable throwable) {
- System.out.println("事件失败了, error message : " + throwable.getMessage());
- }
- }
该异常处理器的一个缺点是不知道哪个事件出错了。
1.2、配置异常处理器:
- <!-- 名字必须是applicationEventMulticaster和messageSource是一样的,默认找这个名字的对象 -->
- <!-- 名字必须是applicationEventMulticaster,因为AbstractApplicationContext默认找个 -->
- <!-- 如果找不到就new一个,但不是异步调用而是同步调用 -->
- <bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
- <!-- 注入任务执行器 这样就实现了异步调用(缺点是全局的,要么全部异步,要么全部同步(删除这个属性即是同步)) -->
- <property name="taskExecutor" ref="executor"/>
- <property name="errorHandler" ref="myErrorHandler"/>
- </bean>
- <bean id="myErrorHandler" class="com.sishuok.error.MyErrorHandler"/>
当执行的ApplicationListener中有异常时会回调该ErrorHandler,但是从目前的回调实现来看,适合做日志记录,其他的无意义。本文是使用的代码基于《详解Spring事件驱动模型》。
2、异步调度异常处理器
在异步调度中也提供了相应的异常处理器进行捕获来记录异常:
- public class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
- @Override
- public void handleUncaughtException(Throwable throwable, Method method, Object... args) {
- System.out.println("调用异步任务出错了, message : " + throwable.getMessage());
- }
- }
如上异常处理器的好处比事件机制的好处在于多了出错的位置和参数,可以根据这些信息进行相应的处理。
配置文件:
- <bean id="asyncExceptionHandler" class="com.github.zhangkaitao.spring.service.MyAsyncExceptionHandler"/>
- <task:annotation-driven exception-handler="asyncExceptionHandler" proxy-target-class="true" />
异步Service:
- @Service
- @Async
- public class AsyncService {
- public void throwException() {
- throw new RuntimeException("error");
- }
- public String asyncGet1() {
- return "123";
- }
- //返回值必须是ListenableFuture/Future,因为是实现的问题(return ((AsyncListenableTaskExecutor) executor).submitListenable(task);)
- public ListenableFuture<String> asyncGet2() {
- return new AsyncResult<String>("123");
- }
- }
当调用throwException时就会抛出异常并被异常处理器捕获到。
另外在之前文章中没有介绍异步方法的返回值,其返回值支持Future/ListenableFuture;然后调用者可以在此等待。Spring 4.1提供了AsyncResult(实现了ListenableFuture)用于返回异步结果。
ListenableFuture提供了新的回调(SuccessCallback和FailureCallback):
- ListenableFuture<String> listenableFuture = asyncService.asyncGet2();
- SuccessCallback<String> successCallback = new SuccessCallback<String>() {
- @Override
- public void onSuccess(String str) {
- System.out.println("异步回调成功了, return : " + str);
- }
- };
- FailureCallback failureCallback = new FailureCallback() {
- @Override
- public void onFailure(Throwable throwable) {
- System.out.println("异步回调失败了, exception message : " + throwable.getMessage());
- }
- };
- listenableFuture.addCallback(successCallback, failureCallback);
- Assert.assertEquals("123", listenableFuture.get());
个人感觉事件机制中的异常处理考虑不周。