我们上一篇文章是建立了一个自定义的异常类,来代替了原始的Exception类。在Serice层抛出异常,然后要在Action层捕获这个异常,这样的话在每个Action中都要有try{}catch{}代码,这样很麻烦啊,那怎么办?定义一个统一的异常处理器类。所有的异常都不用自己去捕获了,统一进行处理就好。
编写流程:
我们这里用的是SpringMvc的框架,所以要先知道SpingMvc的执行流程:
Springmvc提供统一处理器机制,springmvc的前端控制器在调用适配器,去调用action,过程中如果发生异常,前端控制器交给异常处理器进行异常处理。
前端控制器源代码:
我们来讲一下Springmvc中的异常处理机制:
进入到:
我们看一下DispatcherServlet的源码:
这个类的核心函数就是: protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
我们在这个函数里面看到这个:
processHandlerException就是处理异常的。
进入到这个函数看一下:
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // Check registered HandlerExceptionResolvers... ModelAndView exMv = null;
//handlerExceptionResolvers是容器加载的时候把继承了HandlerExceptionResolver接口的类全部放入了handlerExceptionResolvers这个List里面。,具体是
//private void initHandlerExceptionResolvers(ApplicationContext context)这个函数完成.initHandlerExceptionResolvers函数里面有一句话
// Map<String, HandlerExceptionResolver> matchingBeans =
// BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); // 遍历每一个实现了handlerExceptionResolver 接口的类,
然后调用它的 resolveException 方法进行异常处理 if (exMv != null) { break; } } if (exMv != null) { if (exMv.isEmpty()) { return null; } // We might still need view name translation for a plain error model... if (!exMv.hasView()) { exMv.setViewName(getDefaultViewName(request)); } if (logger.isDebugEnabled()) { logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); } WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); return exMv; } throw ex; }
观察代码,发现在这里会遍历 Ioc 容器中的所有实现 handlerExceptionResolver 接口的类,然后调用它的 resolveException 方法进行异常处理,并返回具体的error page(错误页面)。
这个博客讲得很好:http://blog.csdn.net/u012420654/article/details/52141807
总结:也就是说我们要写自定义的异常处理器,就需要继承handlerExceptionResolver 接口。然后实现里面的resolveException方法,这样出现了错误之后就会去
执行我们自己写的resolveException()方法。
接下里我们写自己的异常处理器。
package yycg.base.process.exception; import java.io.IOException; import java.lang.reflect.Method; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import yycg.base.process.result.ExceptionResultInfo; import yycg.base.process.result.ResultInfo; //全局异常处理器 public class ExceptionResolverCustom implements HandlerExceptionResolver{ //json转换器 private HttpMessageConverter<ExceptionResultInfo> jsonMessageConverter; public HttpMessageConverter<ExceptionResultInfo> getJsonMessageConverter() { return jsonMessageConverter; } public void setJsonMessageConverter( HttpMessageConverter<ExceptionResultInfo> jsonMessageConverter) { this.jsonMessageConverter = jsonMessageConverter; } //前端控制器 //handler就是最后要执行的Action类,Springmvc是面向方法开发的。handler就包装了一个方法 //这个方法就对应于URL的那个方法, @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //输出异常信息 ex.printStackTrace(); //在springmvc中,action是被Handle封装起来的,action的方法也被封装到Handle里面了。 //转成springmvc底层对象,这个底层对象就是对action方法进行封装的那个对象 HandlerMethod handlerMethod=(HandlerMethod)handler;//这个对象里面只有一个方法。 Method method=handlerMethod.getMethod();//把这个方法取出来。 //查找method这个方法上是否有ResponBody这个注解 ResponseBody responseBody=AnnotationUtils.findAnnotation(method, ResponseBody.class); if(responseBody!=null) { //这里说明发生异常的Action是我们本来正常情况下要返回JSON数据的,我们在这里做的处理时是把这个错误拿出来我们自己转成json
//再输出到页面上
return this.resolveJsonException(request, response, handlerMethod, ex);
}
//这里说明发生异常的那个Action方法没有用@Responbody定义。正常情况也不是返回json格式的数据的,那我们就没有必要转换成json了。
//解析异常
ExceptionResultInfo exceptionResultInfo=resolveExceptioncustom(ex);
//将异常信息在异常页面输出。
request.setAttribute("exceptionResultInfo", exceptionResultInfo.getResultInfo());
//转向错误页面:用Springmvc的方式去转
ModelAndView modelAndView=new ModelAndView();
modelAndView.addObject("exceptionResultInfo", exceptionResultInfo.getResultInfo()); modelAndView.setViewName("/base/error");
return modelAndView; }
private ModelAndView resolveJsonException(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) { //解析异常 ExceptionResultInfo exceptionResultInfo=resolveExceptioncustom(ex); HttpOutputMessage outputMessage=new ServletServerHttpResponse(response); try { //将exceptionResultInfo转成json然后输出,这里直接通过HttpMessageConverter将exceptionResultInfo转化为 //JSON,然后直接在HttpOutputMessage这个对象中把转化而成的JSON信息写入。输出到浏览器客户端。 jsonMessageConverter.write(exceptionResultInfo,MediaType.APPLICATION_JSON,outputMessage ); } catch (HttpMessageNotWritableException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return new ModelAndView(); } //解析异常,看是不是我们自己定义的异常 private ExceptionResultInfo resolveExceptioncustom( Exception ex) { ResultInfo resultInfo=null; //这样的话就是系统的自定义异常 if(ex instanceof ExceptionResultInfo) { //解析系统的自定义的异常, resultInfo=((ExceptionResultInfo) ex).getResultInfo(); }else { //不属于系统自定义异常的话就是未知错误的异常,就是不可知的异常 resultInfo=new ResultInfo(); resultInfo.setType(ResultInfo.TYPE_RESULT_FAIL); resultInfo.setMessage("未知错误异常"); } return new ExceptionResultInfo(resultInfo); } }
总结:就是说浏览器进入到了Action中的方法中,在执行过程中,发生了异常。然后触发了我们的自定义异常处理器。进入到了resolveException()这个方法。
在这个方法里面首先要做的就是把封装到Handle中的那个Action取出来,然后把这个Action对象中执行的那个方法取出来。看这个方法有没有ResponBody这个注解这个注解,有的话就是说使我们自己定义的异常信息,比如账号重复什么的,我们是要求把这个“账号重复”这类异常信息变成JSON信息放到客户端的,所以就把这个异常信息转化为json格式输出到页面。
然后如果发现这个方法没有ResponBody这个注解,那么我们这个就不是我们自定义的异常了,那么我们把这个异常信息解析好之后,通过自己定义的error.jsp页面把错误信息输出。
上面已经写好了异常处理类,接下来我们就把这个异常处理类配置好。
来到Springmvc.xml中:
<bean id="handlerExceptionResolver" 这个id要固定,一个字都不能改。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd "> <!-- 组件扫描 扫描所有标记@Controller类,由于使用自动扫描所以action类不用在spring配置文件中配置 --> <!-- 为什么这里就要用包扫面的方式了而不是用bean 的方式了因为这里的action太多了,配置不过来,只好用扫描了 --> <context:component-scan base-package="yycg.**.action" /> <!-- 处理器映射器和适配器,可以使用mvc注解驱动 --> <mvc:annotation-driven/> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 将jstl的jar包加入工程,默认支持jstl --> <!-- 前缀和后缀可以去掉的,为了方便开发才加的 --> <property name="prefix" value="/WEB-INF/jsp" /> <property name="suffix" value=".jsp" /> </bean>
<!-- json提供的json转换器 --> <bean id="jsonmessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"> </bean> <!-- 配置统一异常处理器类 --> <bean id="handlerExceptionResolver" class="yycg.base.process.exception.ExceptionResolverCustom" > <!-- 注入一个json转换器--> <property name="jsonMessageConverter" ref="jsonmessageConverter"/> </bean>
</beans>
然后我们还要在web.xml中进行配置。
在web.xml中添加如下:
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springmvc.xml</param-value> </init-param> <!-- 屏蔽springmvc自动注册的异常处理器 --> <init-param> <param-name>detectAllHandlerExceptionResolvers</param-name> <param-value>false</param-value> </init-param> </servlet>
detectAllHandlerExceptionResolvers的作用:屏蔽自动注册异常处理器,固定使用bean的id为handlerExceptionResolver的异常处理器。(所以自定义的异常处理器的id是要要固定的)
接下来我们可以把之前的在Action中try{}catch的做法给去掉了。达到我们最先的想法。
修改后的代码如下:
@RequestMapping("/addsysusersubmit") public @ResponseBody SubmitResultInfo addsysusersubmit(SysuserQueryVo sysuserQueryVo) throws Exception { ResultInfo resultInfo=new ResultInfo(); resultInfo.setType(ResultInfo.TYPE_RESULT_SUCCESS);//默认是成功的 resultInfo.setMessage("成功了"); SubmitResultInfo submitResultInfo=new SubmitResultInfo(resultInfo); userService.insertSysuser(sysuserQueryVo.getSysuserCustom()); //使用全局异常处理器后不用捕获异常了。 submitResultInfo.setResultInfo(resultInfo); return submitResultInfo; }
到这里,我们的配置就好了接下里就是测试:现在只要在执行Acition里面的任何方法时遇到任何问题都会由这个异常处理器来处理.
测试如下:
1、 在action的提交方法(addsysusersubmit(SysuserQueryVo sysuserQueryVo) )中发生异常,由全局异常处理器进行处理。
2、在action返回jsp方法中发生异常,由全局异常处理器进行处理。
测试方法:我们在随便一个Action的方法中写一句:int i=1/0;
然后就出现了:
测试成功
------------------------------------------------------------------------------------------------------------------------------------------
第二次看自己的博客时有点绕晕。我写一个具体的案例,结合代码来讲解一下:
我们看userAction:里面有这么一段代码:
//添加用户提交 //提交的结果要转json到页面 //提交表单的数据一律使用包装类,这里我们用的是SysuserQueryVo //在SysuserQueryVo中包装了SysuserCustom @RequestMapping("/addsysusersubmit") public @ResponseBody SubmitResultInfo addsysusersubmit(SysuserQueryVo sysuserQueryVo) throws Exception { //将执行结果返回页面 userService.insertSysuser(sysuserQueryVo.getSysuserCustom()); //使用全局异常处理器后不用捕获异常了。 //submitResultInfo.setResultInfo(resultInfo); return ResultUtil.createSubmitResult(ResultUtil.createSuccess(Config.MESSAGE,906 , null)); }
我们看一下userService.insertSysuser(sysuserQueryVo.getSysuserCustom());这个函数:
@Override public void insertSysuser(SysuserCustom sysuserCustom) throws Exception { //参数的校验 //通用的参数合法性校验 Sysuser sysuser=findSysuerByUserid(sysuserCustom.getUserid()); //如果不等于空,就是账号重复了。给出一个警告。 if(sysuser!=null) { //包警告告诉到Action,可以再Aciton中捕获这个异常。 //throw new Exception("单位名称输入错误"); //使用系统自定义的异常类 /*ResultInfo resultInfo=new ResultInfo(); resultInfo.setType(ResultInfo.TYPE_RESULT_FAIL); String message=ResourcesUtil.getValue("resources.messages", "213"); resultInfo.setMessage(message); throw new ExceptionResultInfo(resultInfo);*/ //使用工具类对ResultInfo做一个封装。 ResultInfo resultInfo=ResultUtil.createFail(Config.MESSAGE, 213, null); new ResultUtil().throwExcepion(resultInfo); } //根据用户类型,输入单位名称必须存在对应的单位表中 String groupid=sysuserCustom.getGroupid();//用户类型 String sysmc=sysuserCustom.getSysmc();//单位名称 String userid=null; if(groupid.equals("1")||groupid.equals("2")) {//监督单位 //根据单位名称查询单位的信息。 Userjd userjd=this.findUserJdByMc(sysmc); if(userjd==null) { //throw new Exception("单位名称输入错误"); //使用系统自定义的异常类 ResultInfo resultInfo=new ResultInfo(); resultInfo.setType(ResultInfo.TYPE_RESULT_FAIL); resultInfo.setMessage("单位名称输入错误"); throw new ExceptionResultInfo(resultInfo); } userid=userjd.getId(); } //卫生室 else if(groupid.equals("3")) { //根据单位名称查询单位的信息,查不到就报错 Useryy useryy=this.findUseryyByMc(sysmc); if(useryy==null) { //throw new Exception("单位名称输入错误"); //使用系统自定义的异常类 ResultInfo resultInfo=new ResultInfo(); resultInfo.setType(ResultInfo.TYPE_RESULT_FAIL); resultInfo.setMessage("单位名称输入错误"); throw new ExceptionResultInfo(resultInfo); } userid=useryy.getId(); } else if(groupid.equals("4")){ Usergys usergys=this.findUsergesByMc(sysmc); if(usergys==null) { //throw new Exception("单位名称输入错误"); //使用系统自定义的异常类 ResultInfo resultInfo=new ResultInfo(); resultInfo.setType(ResultInfo.TYPE_RESULT_FAIL); resultInfo.setMessage("单位名称输入错误"); throw new ExceptionResultInfo(resultInfo); } userid=usergys.getId(); } sysuserCustom.setId(UUIDBuild.getUUID()); sysuserCustom.setSysid(userid); //把数据插入到数据库 //刚开始的时候感到很奇怪,就是在SysuserMapper.xml中写着输入的参数是yycg.base.pojo.po.Sysuser //但是我这里输入的是sysuserCustom,明显不是Sysuser类型啊,其实是没有关系的,因为,这里的sysuserCustom //继承了Sysuser.所以Sysuser里面有的,在SysuserCustom里面都是有的的。 sysuserMapper.insert(sysuserCustom); }
如果账号重复了:执行了
ResultInfo resultInfo=ResultUtil.createFail(Config.MESSAGE, 213, null); new ResultUtil().throwExcepion(resultInfo);这样就触发了异常。
我们这个Action是用@ResponseBody注释的。那么正常情况下我们应该是要返回json数据的。
但是这个抛异常了,不是正常情况了,但是我们也要返回json数据。所以有了
if(responseBody!=null) { //这里说明发生异常的Action是我们本来正常情况下要返回JSON数据的,我们在这里做的处理时是把这个错误拿出来我们自己转成json
//再输出到页面上
return this.resolveJsonException(request, response, handlerMethod, ex);
}
那些没有用@ResponseBody注释的Action方法,他们的异常处理就按照正规思路处理了。