• SpringMVC异常统一处理(异常信息的国际化,日志记录)


    转载自:    https://my.oschina.net/wangdaoliang/blog/789573

     JAVA EE项目中,不管是对底层的数据操作,还是业务层的处理过程,还是控制层的处理,都不可避免的会遇到各种可预知的(业务异常主动抛出)、不可预知的异常需要处理。一般dao层、service层的异常都会直接抛出,最后由controller统一进行处理,每个过程都单独处理异常,且要考虑到异常信息和前端的反馈,代码的耦合度高,不统一,后期维护的工作也多。

           同时还必须考虑异常模块和日志模块、国际化的支持。

           因此需要一种异常处理机制将异常处理解耦出来,这样保证相关处理过程的功能单一,和系统其它模块解耦,也实现了异常信息的统一处理和维护。

           接下来以实际工作中SpringMVC实现异常的统一处理为例。

    分析

           首先看看SpringMVC处理异常的3中方式,进行比较,最终选用一个比较合适的方式。

    1.   SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver;
    2.   SpringMVC异常处理接口HandlerExceptionResolver自定义自己的异常处理器;
    3.   @ExceptionHandler注解实现异常处理;

    简单实践

          对于第一种方式来说,使用SimpleMappingExceptionResolver能够准确显示定义的异常处理页面,进行异常处理,具有集成简单、有良好的扩展性,因为是基于配置的对已有的代码没有侵入性等优点。但是该方法仅仅能够获取到异常信息,对于其他数据的情况不适用。配置方法如下:

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">  
            <!-- 定义默认的异常处理页面,当该异常类型的注册时使用 -->  
            <property name="defaultErrorView" value="error"></property>  
            <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->  
            <property name="exceptionAttribute" value="ex"></property>  
            <!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常也页名作为值 -->  
            <property name="exceptionMappings">  
                <props>  
                    <prop key="cn.basttg.core.exception.BusinessException">error-business</prop>  
                    <prop key="cn.basttg.core.exception.ParameterException">error-parameter</prop>  
          
                    <!-- 这里还可以继续扩展对不同异常类型的处理 -->  
                </props>  
            </property>  
        </bean>

        对于第二种方式,使用实现HandlerExceptionResolver接口的异常处理进行异常处理,具有集成简单、良好的扩展性、对已有代码没有侵入性等优点。同时由于自定义实现,我们可以在处理异常时进行额外的处理(日志的记录、异常信息的国际化等)。项目实际的开发中也是使用的这种集成方案,配置如下:

    <bean id="exceptionResolver"
        class="com.***.**.common.exception.PlatformMappingExceptionResolver">
            <!--配合自定义的异常解析器-->
            <property name="exceptionMappings">
            <props>
                  <prop key="com.***.**.common.exception.BusinessException">error/error</prop>
                  <prop key="java.lang.Exception">error/error</prop>
            </props>
        </property>
    </bean>

        对于第三种方式,通过@ExceptionHandler注解实现异常处理,同样十分灵活,不过这种方式需要在每个controller上都需注解,解决方案是增加一个BaseController类,使用@ExceptionHandler注解声明异常处理,其他controller都继承他。实现方式如下:

    public class BaseController {  
            /** 基于@ExceptionHandler异常处理 */  
            @ExceptionHandler  
            public String exp(HttpServletRequest request, Exception ex) {  
                  
                request.setAttribute("ex", ex);  
                  
                // 根据不同错误转向不同页面  
                if(ex instanceof BusinessException) {  
                    return "error-business";  
                }else if(ex instanceof ParameterException) {  
                    return "error-parameter";  
                } else {  
                    return "error";  
                }  
            }  
        }

        使用这种方法存在侵入性,而且在异常处理时也不能获取异常以外的数据,且Ajax请求产生的异常信息无法反馈给前端。

          综合考虑,使用第二种方式进行异常统一处理方案的设计。

    方案设计

          首先分析下方案应该实现的需求。

    需求

    1.   出错页面跳转: 例如404页面。基于SpringMVC,前端访问某个页面跳转controller的时候出现异常的时候,跳转到错误页面。
    2.   Ajax异常反馈: 前端通过Ajax的方式访问controller获取JSON数据出现异常的时候,需要将异常信息反馈给前端。
    3.   异常信息的日志记录: 配合日志模块,实现异常日志的记录。
    4.   异常信息的国际化:  配合国际化设计实现异常信息的国际化。

    设计

          1、 首先自定义异常解析器,代码清单如下:

    package com.cisdi.ecis.common.exception;
    
    import java.io.PrintWriter;
    import java.io.StringWriter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
    
    import com.cisdi.ecis.common.utils.ExceptionI18Message;
    
    /**
     * 平台异常信息跳转、解析
     */
    public class PlatformMappingExceptionResolver extends
            SimpleMappingExceptionResolver {
        static Logger logger = LoggerFactory.getLogger(PlatformMappingExceptionResolver.class);
        @Override
        protected ModelAndView doResolveException(HttpServletRequest request,
                HttpServletResponse response, Object handler, Exception ex) {
    
            String viewName = determineViewName(ex, request);
            // vm方式返回
            if (viewName != null) {
                if (!( request.getHeader("accept").indexOf("application/json") > -1 || ( request
                        .getHeader("X-Requested-With") != null && request
                        .getHeader("X-Requested-With").indexOf("XMLHttpRequest") > -1 ) )) {
                    // 非异步方式返回
                    Integer statusCode = determineStatusCode(request, viewName);
                    if (statusCode != null) {
                        applyStatusCodeIfPossible(request, response, statusCode);
                    }
                    // 跳转到提示页面
                    return getModelAndView(viewName, ex, request);
                } else {
                    // 异步方式返回
                    try {
                        PrintWriter writer = response.getWriter();
                        writer.write(ExceptionI18Message.getLocaleMessage(ex.getMessage()));
                        response.setStatus(404, ExceptionI18Message.getLocaleMessage(ex.getMessage()));
                            //将异常栈信息记录到日志中
                                            logger.error(getTrace(ex)); 
                        writer.flush();
                    } catch ( Exception e ) {
                        e.printStackTrace();
                    }
                    // 不进行页面跳转
                    return null;
                }
            } else {
                return null;
            }
        }
        public static String getTrace(Throwable t) {
            StringWriter stringWriter= new StringWriter();
            PrintWriter writer= new PrintWriter(stringWriter);
            t.printStackTrace(writer);
            StringBuffer buffer= stringWriter.getBuffer();
            return buffer.toString();
        }
    }

        2、之后在SpringMVC配置文件中配置异常解析器映射路径。

    <!--配置异常映射路径,ajax提示 -->
    <bean id="exceptionResolver"        
            class="com.cisdi.ecis.common.exception.PlatformMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="com.cisdi.ecis.common.exception.BusinessException">error/error</prop>
                <prop key="java.lang.Exception">error/error</prop>
            </props>
        </property>
    </bean>

        3、 异常信息的国际化

            通过上述配置其实就已经满足了方案需求中的大部分需求,还仅剩一个需求:异常信息的国际化。上述代码中有一段代码:

    ExceptionI18Message.getLocaleMessage(ex.getMessage()

         ExceptionI18Message就是根据当前的语言环境得到异常信息,实现细则如下:

    package com.cisdi.ecis.common.utils;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.servlet.support.RequestContext;
    
    public class ExceptionI18Message{
       
        public static String getLocaleMessage(String key){
            HttpServletRequest request =  ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
            RequestContext requestContext = new RequestContext(request);
            return requestContext.getMessage(key);
        }
    
    }

            那么,全局异常处理器写好后,如果使用呢?后端程序员在编码时,可以直接抛出业务异常,但是压入的message应该是国际化文件中的"key",自己在去国际化文件中编写多套语言的key的value。例如:

    ##Exception
    pbs.exception.copyNode=The Exceptioninfo I18n

    之后我们压入的异常信息为pbs.exception.copyNode:

    throw new Exception("pbs.exception.copyNode");

     测试

           到此为止,方案已经设计完毕,简单的测试下是否满足我们的需求吧,对于页面跳转的异常这里就不在测试了,主要在于前端Ajax请求controller抛出业务异常的时候前端是否能够收到反馈。

           前端代码:

    $.ajax({
                    url: "${basePath}/doc/addDocMaterials",
                    type: "post",
                    dataType: "json",
                    data: obj,
                    complete: function(xhr) {
                        console.log(xhr);
                        if (xhr.status == 200 && xhr.responseText != null) {} else {
                            $.messager.alert('#springMessage("message.tip")', xhr.responseText);
                            displayLoad();
                        }
                    }
    });

         之后后端主动抛出业务异常的时候,前端获取到的反馈结果如下:(这里我们就以上面的抛出异常的代码为例)。

            到此为止,关于SpringMVC异常的统一处理方案(国际化、Ajax反馈)结束。

  • 相关阅读:
    面试问题 集锦
    减少 lwip 消耗 的 RAM
    Blocking Master Example QT 自带 的 serial 即 串口 例子
    32位 的变量 用于表示 ms ,可以表示多少天那?
    centos 腾讯云 今天买了 18个月
    Linux BLE 基于 树莓派
    树莓派 4G模块 PPP 拨号 NDIS 拨号
    linux备份还原命令
    centos7中/tmp文件保存天数
    centos7查看可登陆用户
  • 原文地址:https://www.cnblogs.com/Yi-ling/p/14523516.html
Copyright © 2020-2023  润新知