轉載 http://fancyboy2050.iteye.com/blog/1300037
最近使用spring mvc開發一個web系統,發現在controller里發生未捕獲異常時不出日志。
分析DispatcherServlet,初始化handlerExceptionResolvers
Java代碼
- /**
- * Initialize the strategy objects that this servlet uses.
- * <p>May be overridden in subclasses in order to initialize
- * further strategy objects.
- */
- protected void initStrategies(ApplicationContext context) {
- initMultipartResolver(context);
- initLocaleResolver(context);
- initThemeResolver(context);
- initHandlerMappings(context);
- initHandlerAdapters(context);
- // 初始化異常處理支持器
- initHandlerExceptionResolvers(context);
- initRequestToViewNameTranslator(context);
- initViewResolvers(context);
- }
- // 進入初始化處理方法,具體內容就不貼了,主要是先到上下文中搜尋我們自己定義的ExceptionResolvers,如果沒有自定義的resolvers,從默認配置中讀取。
- private void initHandlerExceptionResolvers(ApplicationContext context)
- // 從默認策略中取得默認配置,從DispatcherServlet.properties文件中取得相關的配置策略,但是在spring2.5的mvc jar包中properties文件中沒有HandlerExceptionResolver的默認配置,返回一個EmptyList給handlerExceptionResolvers
- protected List getDefaultStrategies(ApplicationContext context, Class strategyInterface)
分析DispatcherServlet,分發處理請求
Java代碼
- // 從dispatch方法中看到,系統對請求進行具體的邏輯處理部分被catch住了一次exception,然后會使用servlet持有的ExceptionResolver進行處理
- protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HttpServletRequest processedRequest = request;
- HandlerExecutionChain mappedHandler = null;
- int interceptorIndex = -1;
- // Expose current LocaleResolver and request as LocaleContext.
- LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
- LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
- // Expose current RequestAttributes to current thread.
- RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
- ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
- RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
- if (logger.isTraceEnabled()) {
- logger.trace("Bound request context to thread: " + request);
- }
- try {
- ModelAndView mv = null;
- boolean errorView = false;
- try {
- processedRequest = checkMultipart(request);
- // Determine handler for the current request.
- mappedHandler = getHandler(processedRequest, false);
- if (mappedHandler == null || mappedHandler.getHandler() == null) {
- noHandlerFound(processedRequest, response);
- return;
- }
- // Apply preHandle methods of registered interceptors.
- HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
- if (interceptors != null) {
- for (int i = 0; i < interceptors.length; i++) {
- HandlerInterceptor interceptor = interceptors[i];
- if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
- triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
- return;
- }
- interceptorIndex = i;
- }
- }
- // Actually invoke the handler.
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- // Do we need view name translation?
- if (mv != null && !mv.hasView()) {
- mv.setViewName(getDefaultViewName(request));
- }
- // Apply postHandle methods of registered interceptors.
- if (interceptors != null) {
- for (int i = interceptors.length - 1; i >= 0; i--) {
- HandlerInterceptor interceptor = interceptors[i];
- interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
- }
- }
- }
- catch (ModelAndViewDefiningException ex) {
- logger.debug("ModelAndViewDefiningException encountered", ex);
- mv = ex.getModelAndView();
- }
- // 這里catch住controller拋出的異常,使用持有的ExceptionResolver處理,當沒有配置自己的處理器時,程序會將異常繼續往上拋出,最終交給我們的容器處理
- catch (Exception ex) {
- Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
- mv = processHandlerException(processedRequest, response, handler, ex);
- errorView = (mv != null);
- }
- // Did the handler return a view to render?
- if (mv != null && !mv.wasCleared()) {
- render(mv, processedRequest, response);
- if (errorView) {
- WebUtils.clearErrorRequestAttributes(request);
- }
- }
- else {
- if (logger.isDebugEnabled()) {
- logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
- getServletName() + "': assuming HandlerAdapter completed request handling");
- }
- }
- // Trigger after-completion for successful outcome.
- triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
- }
- // 當沒有配置ExceptionResolver時,異常將到達這里,最終拋出
- catch (Exception ex) {
- // Trigger after-completion for thrown exception.
- triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
- throw ex;
- }
- catch (Error err) {
- ServletException ex = new NestedServletException("Handler processing failed", err);
- // Trigger after-completion for thrown exception.
- triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
- throw ex;
- }
- finally {
- // Clean up any resources used by a multipart request.
- if (processedRequest != request) {
- cleanupMultipart(processedRequest);
- }
- // Reset thread-bound context.
- RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
- LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
- // Clear request attributes.
- requestAttributes.requestCompleted();
- if (logger.isTraceEnabled()) {
- logger.trace("Cleared thread-bound request context: " + request);
- }
- }
- }
轉載: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代碼
- // Check registerer HandlerExceptionResolvers...
- ModelAndView exMv = null;
- for (Iterator it = this.handlerExceptionResolvers.iterator(); exMv == null && it.hasNext();) {
- HandlerExceptionResolver resolver = (HandlerExceptionResolver) it.next();
- exMv = resolver.resolveException(request, response, handler, ex);
- }
- if (exMv != null) {
- if (logger.isDebugEnabled()) {
- logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
- }
- WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
- return exMv;
- }
可以看到可以插入自己的HandlerExceptionResover來搞定這個問題,我們可以在resolveException方法任意處理異常和log。也可以
把錯誤信息個性化后傳到view層顯示。
我們只有簡單的需求,就是把沒有catch的異常記入log,將異常的完整信息放在錯誤頁面的一個隱藏的區域,方便查找出現錯誤的原因。
首先我們實現HandlerExceptionResolver
Java代碼
- package com.qunar.advertisement.exception;
- import java.util.HashMap;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.apache.log4j.Logger;
- import org.springframework.web.servlet.HandlerExceptionResolver;
- import org.springframework.web.servlet.ModelAndView;
- import com.qunar.advertisement.utils.StringPrintWriter;
- public class QADHandlerExceptionResolver implements HandlerExceptionResolver{
- private static Logger logger = Logger.getLogger(QADHandlerExceptionResolver.class);
- @Override
- public ModelAndView resolveException(HttpServletRequest request,
- HttpServletResponse response, Object handler, Exception ex) {
- logger.error("Catch Exception: ",ex);//把漏網的異常信息記入日志
- Map<String,Object> map = new HashMap<String,Object>();
- StringPrintWriter strintPrintWriter = new StringPrintWriter();
- ex.printStackTrace(strintPrintWriter);
- map.put("errorMsg", strintPrintWriter.getString());//將錯誤信息傳遞給view
- return new ModelAndView("error",map);
- }
- }
我們還需要一個輔助的類StringPrintWriter,因為ex.printStackTrace參數只有個PrintWriter類型的,java自帶的StringWriter
不可用,所以我們需要自己實現一個裝飾器的StringPrintWriter。
Java代碼
- package com.qunar.advertisement.utils;
- import java.io.PrintWriter;
- import java.io.StringWriter;
- public class StringPrintWriter extends PrintWriter{
- public StringPrintWriter(){
- super(new StringWriter());
- }
- public StringPrintWriter(int initialSize) {
- super(new StringWriter(initialSize));
- }
- public String getString() {
- flush();
- return ((StringWriter) this.out).toString();
- }
- @Override
- public String toString() {
- return getString();
- }
- }
我們只需要在xml中配置一下就可以了:
Xml代碼
- <bean class="com.qunar.advertisement.exception.QADHandlerExceptionResolver">
- </bean>
我們在錯誤頁面隱藏區域顯示錯誤信息:
Html代碼
- <div style="display:none;">
- <c:out value="${errorMsg}"></c:out>
- </div>
