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

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

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

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

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

分析

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

  1. Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver;
  2. Spring MVC异常处理接口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页面。基于Spring MVC,前端访问某个页面跳转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;

/**
 *
 * @author LCore
 *
 * 平台异常信息跳转、解析
 *
 */
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、之后在Spring MVC配置文件中配置异常解析器映射路径。

<!--配置异常映射路径,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);
    }

}

关于国际化:http://1.liangtao.sinaapp.com/?p=756

后端程序员在编码时,可以直接抛出业务异常,但是压入的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();
                    }
                }
});

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

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

时间: 2024-10-06 20:38:04

Spring MVC异常统一处理(异常信息的国际化,日志记录)的相关文章

spring mvc controller中的异常封装

http://abc08010051.iteye.com/blog/2031992 一直以来都在用spring mvc做mvc框架,我使用的不是基于注解的,还是使用的基于xml的,在controller里一般都会加上一个异常捕捉,能分析出来的异常,提示出具体信息,不能预料的异常,返回一个统一的异常提示信息,未封装前的代码为: Java代码   public ModelAndView addBigDeal(HttpServletRequest request, HttpServletRespons

Spring Mvc 笔记二之异常和文件上传

spring mvc的异常与文件上传 1.异常: spring注解版的异常有局部异常和全局异常                1.局部异常对单个controller有效;(在controller类写一个处理异常的方法) @ExceptionHandler(value={UserException.class}) public String handlerExceptionTest(UserException e ,HttpServletRequest request){ request.setA

JRebel热部署spring mvc时发生的异常。

JRebel: ERROR org.zeroturnaround.bundled.javassist.CannotCompileException: [source error] no such class: handlerMethods        at org.zeroturnaround.bundled.javassist.CtNewMethod.make(JRebel:79)        at org.zeroturnaround.bundled.javassist.CtNewMet

Spring MVC自定义统一异常处理类,并且在控制台中输出错误日志

在使用SimpleMappingExceptionResolver实现统一异常处理后(参考Spring MVC的异常统一处理方法), 发现出现异常时,log4j无法在控制台输出错误日志.因此需要自定义一个继承至SimpleMappingExceptionResolver的 RrtongMappingExceptionResolver类,在RrtongMappingExceptionResolver中通过 log.error(ex.getMessage())的方式输出日志到控制台上.以下是具体的配

通过拦截器Interceptor实现Spring MVC中Controller接口访问信息的记录

java web工程项目使用了Spring+Spring MVC+Hibernate的结构,在Controller中的方法都是用于处理前端的访问信息,Controller通过调用Service进行业务处理后给前端返回ModelAndView对象或者只返回Json格式数据.如果能够获得Http请求在后端程序中处理的相关信息,对于开发和调试时十分方便的.工程中使用了Spring MVC的Interceptor对所有Http请求及其响应进行拦截,从而获取到本次访问接口信息以及程序处理时长等信息,特意在

spring mvc 中web.xml配置信息解释

在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人的,毕竟人家写的不错,自己也就不重复造轮子了,只是略加点了自己的修饰. 首先可以肯定的是,加载顺序与它们在 web.xml 文件中的先后顺序无关.即不会因为 filter 写在 listener 的前面而会先加载 filter.最终得出的结论是:listener -> filter -> servlet 同时还存在着这样一种配置节:context-param,它用于向 Servlet

spring mvc 异常统一处理 【转】

SpringMVC 提供的异常处理主要有两种方式,一种是直接实现自己的HandlerExceptionResolver,另一种是使用注解的方式实现一个专门用于处理异 常的Controller——ExceptionHandler.前者当发生异常时,页面会跳到指定的错误页面,后者同样,只是后者会在每个 controller中都需要加入重复的代码.如何进行简单地统一配置异常,使得发生普通错误指定到固定的页面,ajax发生错直接通过js获取,展现给 用户,变得非常重要.下面先介绍下2种异常处理方式,同时

Spring MVC处理异常的4种方式

http://blog.csdn.net/ufo2910628/article/details/40399539 http://my.oschina.net/CandyDesire/blog/333340 Spring MVC处理异常有3种方式: (1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver: (2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器: (3)使用@Excepti

使用Spring MVC统一异常处理实战

1 描写叙述 在J2EE项目的开发中.无论是对底层的数据库操作过程.还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的.不可预知的异常须要处理.每一个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一.维护的工作量也非常大. 那么,能不能将全部类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的. 以下将介绍使用Spring MVC统一处理异常的解决和实现过程. 2 分析 Spring MVC处理异