使用Spring MVC HandlerExceptionResolver处理异常

转载 http://fancyboy2050.iteye.com/blog/1300037

最近使用spring mvc开发一个web系统,发现在controller里发生未捕获异常时不出日志。 


分析DispatcherServlet,初始化handlerExceptionResolvers 
Java代码  收藏代码
  1.         /** 
  2.      * Initialize the strategy objects that this servlet uses. 
  3.      * <p>May be overridden in subclasses in order to initialize 
  4.      * further strategy objects. 
  5.      */  
  6.     protected void initStrategies(ApplicationContext context) {  
  7.         initMultipartResolver(context);  
  8.         initLocaleResolver(context);  
  9.         initThemeResolver(context);  
  10.         initHandlerMappings(context);  
  11.         initHandlerAdapters(context);  
  12. // 初始化异常处理支持器  
  13.         initHandlerExceptionResolvers(context);  
  14.         initRequestToViewNameTranslator(context);  
  15.         initViewResolvers(context);  
  16.     }  
  17.   
  18. // 进入初始化处理方法,具体内容就不贴了,主要是先到上下文中搜寻我们自己定义的ExceptionResolvers,如果没有自定义的resolvers,从默认配置中读取。  
  19. private void initHandlerExceptionResolvers(ApplicationContext context)  
  20.   
  21. // 从默认策略中取得默认配置,从DispatcherServlet.properties文件中取得相关的配置策略,但是在spring2.5的mvc jar包中properties文件中没有HandlerExceptionResolver的默认配置,返回一个EmptyList给handlerExceptionResolvers  
  22. protected List getDefaultStrategies(ApplicationContext context, Class strategyInterface)  


分析DispatcherServlet,分发处理请求 
Java代码  收藏代码
  1. // 从dispatch方法中看到,系统对请求进行具体的逻辑处理部分被catch住了一次exception,然后会使用servlet持有的ExceptionResolver进行处理  
  2. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  
  3.         HttpServletRequest processedRequest = request;  
  4.         HandlerExecutionChain mappedHandler = null;  
  5.         int interceptorIndex = -1;  
  6.   
  7.         // Expose current LocaleResolver and request as LocaleContext.  
  8.         LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();  
  9.         LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);  
  10.   
  11.         // Expose current RequestAttributes to current thread.  
  12.         RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();  
  13.         ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);  
  14.         RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);  
  15.   
  16.         if (logger.isTraceEnabled()) {  
  17.             logger.trace("Bound request context to thread: " + request);  
  18.         }  
  19.           
  20.         try {  
  21.             ModelAndView mv = null;  
  22.             boolean errorView = false;  
  23.   
  24.             try {  
  25.                 processedRequest = checkMultipart(request);  
  26.   
  27.                 // Determine handler for the current request.  
  28.                 mappedHandler = getHandler(processedRequest, false);  
  29.                 if (mappedHandler == null || mappedHandler.getHandler() == null) {  
  30.                     noHandlerFound(processedRequest, response);  
  31.                     return;  
  32.                 }  
  33.   
  34.                 // Apply preHandle methods of registered interceptors.  
  35.                 HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();  
  36.                 if (interceptors != null) {  
  37.                     for (int i = 0; i < interceptors.length; i++) {  
  38.                         HandlerInterceptor interceptor = interceptors[i];  
  39.                         if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {  
  40.                             triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);  
  41.                             return;  
  42.                         }  
  43.                         interceptorIndex = i;  
  44.                     }  
  45.                 }  
  46.   
  47.                 // Actually invoke the handler.  
  48.                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
  49.                 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
  50.   
  51.                 // Do we need view name translation?  
  52.                 if (mv != null && !mv.hasView()) {  
  53.                     mv.setViewName(getDefaultViewName(request));  
  54.                 }  
  55.   
  56.                 // Apply postHandle methods of registered interceptors.  
  57.                 if (interceptors != null) {  
  58.                     for (int i = interceptors.length - 1; i >= 0; i--) {  
  59.                         HandlerInterceptor interceptor = interceptors[i];  
  60.                         interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);  
  61.                     }  
  62.                 }  
  63.             }  
  64.             catch (ModelAndViewDefiningException ex) {  
  65.                 logger.debug("ModelAndViewDefiningException encountered", ex);  
  66.                 mv = ex.getModelAndView();  
  67.             }  
  68. // 这里catch住controller抛出的异常,使用持有的ExceptionResolver处理,当没有配置自己的处理器时,程序会将异常继续往上抛出,最终交给我们的容器处理  
  69.             catch (Exception ex) {  
  70.                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);  
  71.                 mv = processHandlerException(processedRequest, response, handler, ex);  
  72.                 errorView = (mv != null);  
  73.             }  
  74.   
  75.             // Did the handler return a view to render?  
  76.             if (mv != null && !mv.wasCleared()) {  
  77.                 render(mv, processedRequest, response);  
  78.                 if (errorView) {  
  79.                     WebUtils.clearErrorRequestAttributes(request);  
  80.                 }  
  81.             }  
  82.             else {  
  83.                 if (logger.isDebugEnabled()) {  
  84.                     logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +  
  85.                             getServletName() + "': assuming HandlerAdapter completed request handling");  
  86.                 }  
  87.             }  
  88.   
  89.             // Trigger after-completion for successful outcome.  
  90.             triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);  
  91.         }  
  92. // 当没有配置ExceptionResolver时,异常将到达这里,最终抛出  
  93.         catch (Exception ex) {  
  94.             // Trigger after-completion for thrown exception.  
  95.             triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  
  96.             throw ex;  
  97.         }  
  98.         catch (Error err) {  
  99.             ServletException ex = new NestedServletException("Handler processing failed", err);  
  100.             // Trigger after-completion for thrown exception.  
  101.             triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  
  102.             throw ex;  
  103.         }  
  104.   
  105.         finally {  
  106.             // Clean up any resources used by a multipart request.  
  107.             if (processedRequest != request) {  
  108.                 cleanupMultipart(processedRequest);  
  109.             }  
  110.   
  111.             // Reset thread-bound context.  
  112.             RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);  
  113.             LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);  
  114.   
  115.             // Clear request attributes.  
  116.             requestAttributes.requestCompleted();  
  117.             if (logger.isTraceEnabled()) {  
  118.                 logger.trace("Cleared thread-bound request context: " + request);  
  119.             }  
  120.         }  
  121.     }  

转载:http://fuliang.iteye.com/blog/947191 


Spring MVC的确很强大,在每一个你想的到和想不到的地方都会留下钩子,来插入自定义的实现,透明替换默认实现, 
拦截器堆栈结构设计的非常强大,多种试图的解析,url mapping的多种实现,Locale resolver、Theme resolver 
、multipart file resolver,Excepiton hanlder Resolver等等,能让Spring MVC从1.0到3.0经历巨大变化, 
仍能向后兼容,并支持很酷的RESTful风格和强大的简化xml配置的注解。 
这些功能我们在项目中经常用到,但是Excepiton hanlder Resolver可能是个生僻一点的东东,因为我们通常对错误 
的处理通常不是非常的复杂,很多情况下只是根据异常或者http error code跳转到错误页面,这个是JSP/servlet就可
以搞定,在web.xml配置一下即可。 

今天遇到一个事情,让我想用到HandlerExceptionResolver这个东东来处理异常。今天准备把自助系统进入上线状态, 
所以把log的级别从DEBUG调到INFO,结果没有catch的Runtime异常在log记录,后来跟踪了一下原来Spring把异常处理的log, 
直接使用的是debug,而不是error,所以log级别设置为INFO导致异常没有记录,看了一下spring的源代码: 
Java代码  收藏代码
  1. // Check registerer HandlerExceptionResolvers...  
  2. ModelAndView exMv = null;  
  3. for (Iterator it = this.handlerExceptionResolvers.iterator(); exMv == null && it.hasNext();) {  
  4. HandlerExceptionResolver resolver = (HandlerExceptionResolver) it.next();  
  5. exMv = resolver.resolveException(request, response, handler, ex);  
  6. }  
  7. if (exMv != null) {  
  8. if (logger.isDebugEnabled()) {  
  9. logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);  
  10. }  
  11. WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());  
  12. return exMv;  
  13. }  

可以看到可以插入自己的HandlerExceptionResover来搞定这个问题,我们可以在resolveException方法任意处理异常和log。也可以 
把错误信息个性化后传到view层显示。 
我们只有简单的需求,就是把没有catch的异常记入log,将异常的完整信息放在错误页面的一个隐藏的区域,方便查找出现错误的原因。 
首先我们实现HandlerExceptionResolver 
Java代码  收藏代码
  1. package com.qunar.advertisement.exception;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. import javax.servlet.http.HttpServletRequest;  
  7. import javax.servlet.http.HttpServletResponse;  
  8.   
  9. import org.apache.log4j.Logger;  
  10. import org.springframework.web.servlet.HandlerExceptionResolver;  
  11. import org.springframework.web.servlet.ModelAndView;  
  12.   
  13. import com.qunar.advertisement.utils.StringPrintWriter;  
  14.   
  15. public class QADHandlerExceptionResolver implements HandlerExceptionResolver{  
  16.     private static Logger logger = Logger.getLogger(QADHandlerExceptionResolver.class);  
  17.     @Override  
  18.     public ModelAndView resolveException(HttpServletRequest request,  
  19.             HttpServletResponse response, Object handler, Exception ex) {  
  20.         logger.error("Catch Exception: ",ex);//把漏网的异常信息记入日志  
  21.         Map<String,Object> map = new HashMap<String,Object>();  
  22.         StringPrintWriter strintPrintWriter = new StringPrintWriter();  
  23.         ex.printStackTrace(strintPrintWriter);  
  24.         map.put("errorMsg", strintPrintWriter.getString());//将错误信息传递给view  
  25.         return new ModelAndView("error",map);  
  26.     }  
  27.   
  28. }  

我们还需要一个辅助的类StringPrintWriter,因为ex.printStackTrace参数只有个PrintWriter类型的,java自带的StringWriter 
不可用,所以我们需要自己实现一个装饰器的StringPrintWriter。 
Java代码  收藏代码
  1. package com.qunar.advertisement.utils;  
  2.   
  3. import java.io.PrintWriter;  
  4. import java.io.StringWriter;  
  5.   
  6. public class StringPrintWriter extends PrintWriter{  
  7.   
  8.     public StringPrintWriter(){  
  9.         super(new StringWriter());  
  10.     }  
  11.      
  12.     public StringPrintWriter(int initialSize) {  
  13.           super(new StringWriter(initialSize));  
  14.     }  
  15.      
  16.     public String getString() {  
  17.           flush();  
  18.           return ((StringWriter) this.out).toString();  
  19.     }  
  20.      
  21.     @Override  
  22.     public String toString() {  
  23.         return getString();  
  24.     }  
  25. }  

我们只需要在xml中配置一下就可以了: 
Xml代码  收藏代码
  1. <bean class="com.qunar.advertisement.exception.QADHandlerExceptionResolver">  
  2. </bean>  

我们在错误页面隐藏区域显示错误信息: 
Html代码  收藏代码
  1. <div style="display:none;">  
  2.      <c:out value="${errorMsg}"></c:out>  
  3. </div>  
原文地址:https://www.cnblogs.com/chenying99/p/2712587.html