1. 程式人生 > >struts2之json請求的異常處理方案

struts2之json請求的異常處理方案

大家都知道,使用struts2的異常處理機制,只要幾行配置,就可以在發生異常時,跳轉到我們指定的頁面,並顯示出相應的異常資訊,具體的使用操作過程如下:

1)struts.xml

<struts>
<include file="struts-default.xml"></include>
    <constant name="struts.devMode" value="true" /><!-- 實現國際化資原始檔和struts配置檔案自動重新載入,不需要重啟伺服器 -->
    <constant name="struts.ui.theme" value="simple" />
    <constant name="struts.action.extension" value="," /><!-- 不要字尾 -->
    <constant name="struts.enable.SlashesInActionNames" value="true" /><!-- 使用"/"的方式 -->
    <package name="hatch" namespace="" extends="json-default"><!-- json支援  -->
...

<!-- 定義全域性檢視 -->
    <global-results>
	<result name="login" type="redirectAction">login</result>
	<result name="404">/WEB-INF/view/404.jsp</result>
	<result name="500">/WEB-INF/view/500.jsp</result>
    </global-results>
    <!-- 定義全域性異常-->
    <global-exception-mappings>
	<exception-mapping result="500" exception="java.lang.Exception"/><!-- 出現異常時,將頁面轉到500這個錯誤頁面 -->
    </global-exception-mappings>
...

</struts>




2)500.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>程式異常</title>
<%@include file="/WEB-INF/view/common/common.jspf" %>
<style type="text/css">
*{margin:0;padding:0;}
.errTitle{min-width:300px;width:90%;height:50px;line-height:50px;border:1px solid #999999;border-radius:5px;background:#0169B2;text-align:center;word-spacing:10px;font-family:'黑體';font-size:24px;margin:10px 70px;padding: 4px 4px 4px 6px;position:relative;}
.showErrWrap{font-size:10px;position:absolute;right:10px;bottom:-5px;}
.showErrWrap a:link,.showErrWrap a:visited{text-decoration:none;color:black;}
.showErrWrap a:hover{text-decoration:underline;color:yellow;}
.showErrWrap span{margin:0 4px;color:black;}
.errStack{min-width:300px;width:90%;font-family:"Courier New", Courier, monospace;border:0 none;margin:10px 70px;overflow:auto;padding:4px;}
</style>
<script type="text/javascript">
$(function(){
	$('#showErrBtn').toggle(function(){
		$('#showErrBtn').text('關閉詳情');
		$('.errStack').slideDown('normal');
	},function(){
		$('.errStack').slideUp('normal');
		$('#showErrBtn').text('檢視詳情');
	});
});
if (window.parent != window) {
	window.parent.location.href = window.location.href;
}
</script>

</head>
<body>
<div class="errTitle">程 序 出 現 異 常!<span class="showErrWrap"><a href="javascript:void(0);" id="showErrBtn">檢視詳情</a><span>|</span><a href="javascript:void(0);" onclick="javascript:history.go(-1);">返回上一頁</a></span></div>
<div class="errStack" style="display:none;"><pre>
<s:property value="exceptionStack"/><!-- 異常資訊 -->
</pre>
</div>
</body>
</html>

假設在UserAction.java中有一個跳轉到列表頁面的方法,如下:

/* 使用者列表檢視 */
public String list() {
	int i = 10/0;
	return "list";
}

這樣,在出現異常時,就能到這個頁面了,效果大約是下面的樣子:

但現在我很多請求處理,使用的都是ajax提交請求並獲取資料的方式,這種情況下,預設的異常處理就力不從心了。跑個題兒先,稍微介紹下struts2中ajax請求的配置方式,以下以使用者列表為例:

1)新增jar包支援:struts2-json-plugin-2.3.4.jar

2)在自己的struts.xml中,package繼承json-default

3)返回型別為json

4)Action中新增設定返回的資料的值

如下是配置:

<struts>
	<constant name="struts.devMode" value="true" /><!-- 實現國際化資原始檔和struts配置檔案自動重新載入,不需要重啟伺服器 -->
	<constant name="struts.ui.theme" value="simple" />
	<constant name="struts.action.extension" value="," /><!-- 不要字尾 -->
	<constant name="struts.enable.SlashesInActionNames" value="true" /><!-- 使用"/"的方式 -->
	<package name="hatch" namespace="" extends="json-default"> <!-- json支援 -->
...
<action name="user/*" class="userAction" method="{1}">
            <result name="list">/WEB-INF/view/sys/user/list.jsp</result>
            <result name="saveOrUpdate">/WEB-INF/view/sys/user/saveOrUpdate.jsp</result>
            <result name="assginRole">/WEB-INF/view/sys/user/assginRole.jsp</result>
            <result type="json">
                <param name="root">dataMap</param>
                <param name="excludeProperties">rows\[\d+\]\.department.parent,rows\[\d+\]\.department.users,rows\[\d+\]\.department.children,rows\[\d+\]\.roles</param>
            </result><!-- ajax請求的返回檢視 -->
        </action>

如下是頁面:
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<%@include file="/WEB-INF/view/common/common.jspf"%>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<title>使用者列表</title>
		<script type="text/javascript">
		$(function(){
			//使用者列表初始化
			$('#dg').datagrid({
				striped : true,
				rownumbers : true,
				singleSelect : false,
				pagination : true,
				pageSize : 10,
				fitColumn : true,
				dataType : 'json',
				animate : true,
				loadMsg : '請稍候...',
		        url:'${ctx }/sys/user/doList', 
		        columns:[[
						  {field:'ck',checkbox:true,align:'center'},
		                  {title:'序號',field:'id',align:'center'},
		                  {title:'登入名',field:'username',align:'center'},
		                  {title:'名稱',field:'name',align:'center'},
		                  {title:'性別',field:'gender',align:'center',formatter:function(v){ return v==1?'男':'女' }},
		                  {title:'手機',field:'phoneNumber',align:'center'},
		                  {title:'email',field:'email',align:'center'},
		                  {title:'所屬部門',field:'department',align:'center',formatter:function(v){if(v)return v['name'];}},
		                  {title:'操作',field:'Operation',align:'center',formatter: operationFormate}
		                 ]], 
		    });//easyui的列表請求,返回資料為json
			//新增操作列
			function operationFormate(value,node){
				  var str='<a style="color:green;text-decoration:none;margin-right:4px;" href="javascript:void(0);" onclick="update('+node.id+')">修改</a>'
				  	+'<a style="color:red;text-decoration:none;margin-right:4px;" href="javascript:void(0);" onclick="del('+node.id+')">刪除</a>'
				  	+'<a style="color:green;text-decoration:none;margin-right:4px;" href="javascript:void(0);" onclick="assignrole('+node.id+')">角色分配</a>'
				  	+'<a style="color:red;text-decoration:none;" href="javascript:void(0);" onclick="resetPwd('+node.id+')">密碼重置</a>';
		          return str;  
		    }
			//查詢使用者
			$('#queryBtn').click(function(){
				var queryParams = $('#dg').datagrid('options').queryParams;
				var name = $.trim($('#queryForm').find('input[name=name]').val());
				var gender = $.trim($('#queryForm').find('select[name=gender]').val());
		        queryParams.name = name;  
		        queryParams.gender = gender;  
		        $('#dg').datagrid('options').queryParams=queryParams;    
				$('#dg').datagrid('reload');
			});
			
			//新增使用者
			$('#addBtn').click(function(){
				window.location.href="${ctx}/sys/user/save";
			});
			
			//刪除使用者
			$('#delBtn').click(function(){
				var ss = new Array();
				var rows = $('#dg').datagrid('getSelections');
				for(var i=0; i<rows.length; i++){
					var row = rows[i];
					ss.push(row.id);
				}
				del(ss.join(','));
			});
			
		});
		//修改使用者
		function update(id){
			window.location.href="${ctx}/sys/user/update?id="+id;
		}
		//刪除使用者
		function del(ids){
			$.messager.confirm('確認框', '確認要刪除嗎?此操作是不可恢復的', function(r){
				if (r){ 
					var url = '${ctx }/sys/user/del';
					$.post(url, {
						ids:ids
					}, function(data){
						if(data.result==0){
							$('#dg').datagrid('reload'); 	
						}else{
							$.messager.alert('error','失敗');
						}
						alert(data.result);
					},'json');
				}
			});
		}
		 //分配角色
		function assignrole(id){
			window.location.href="${ctx}/sys/user/assginRole?id="+id;	
		}
		 //密碼重置
		function resetPwd(id){
			var url = '${ctx }/sys/user/doResetPwd1';
			$.post(url, {
				id:id
			}, function(data){
				if(data.result==0){
					$.messager.alert('資訊提示','修改成功,密碼重置為111111!','info');
				}else{
					var msg = data.msg; 
					$.messager.alert('錯誤提示','操作失敗!','error');
					window.location = "${ctx}/jsonHandlerAction";
				}
			},'json');
		}
	</script>
	</head>
	<body>
		<div class="ptitle">系統管理>>使用者列表</div>
		<div class="content-wrap">
		<div class="pcontent">
		<div class="easyui-panel toolbar">
			<a href="javascript:void(0);" class="easyui-linkbutton" iconCls="icon-add" plain="true" id="addBtn">新建</a>
			<a href="javascript:void(0);" class="easyui-linkbutton" iconCls="icon-remove" plain="true" id="delBtn">刪除</a>
			<s:form action="/user/doList" namespace="/"  method="post" id="queryForm">
			名稱:<s:textfield name="name"/>
			性別:<s:select list="#{null:'全部',1:'男',2:'女'}" name="gender"/>
			<a href="javascript:void(0);" class="easyui-linkbutton" iconCls="icon-search" id="queryBtn">Search</a>
			</s:form>
		</div>
		<!-- 列表 -->
		<table id="dg" title=""></table>
		</div>
		</div>
	</body>
如下是Action
/* 使用者 列表資料 */
	public String doList(){
		HttpServletRequest re = ServletActionContext.getRequest();
		dataMap = new HashMap<String, Object>();
		Map<String, Object> cond = MapBeanUtil.transBean2Map(user);
		Page<User> p = new Page<User>();
		cond.put("page", Integer.parseInt(re.getParameter("page")));
		cond.put("rows", Integer.parseInt(re.getParameter("rows")));
		p = userService.findUserForPage(cond);
		dataMap.put("total", p.getTotal());
		dataMap.put("rows", p.getRows());
		return SUCCESS;
	}
效果如下:

以上是json開發的簡單介紹,更詳細的可以下載相關文件。

返回正題,假如正在我在Action中出現異常,可怕的事情:框架將錯誤頁面的html程式碼以html的形式返回來了,以下是用firefox看到的結果:


分析思考:

struts2的異常處理,是基於攔截器的,出現目前的錯誤,就是因為攔截器的異常處理,沒有考慮到ajax非同步請求,那麼是哪個攔截器處理異常呢?開啟struts核心包struts2-core-2.3.4.jar中的struts-default.xml中,檢視下面的資訊:

<interceptors>
            <interceptor name="alias" class="com.opensymphony.xwork2.interceptor.AliasInterceptor"/>
            <interceptor name="autowiring" class="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"/>
            <interceptor name="chain" class="com.opensymphony.xwork2.interceptor.ChainingInterceptor"/>
            <interceptor name="conversionError" class="org.apache.struts2.interceptor.StrutsConversionErrorInterceptor"/>
            <interceptor name="cookie" class="org.apache.struts2.interceptor.CookieInterceptor"/>
            <interceptor name="clearSession" class="org.apache.struts2.interceptor.ClearSessionInterceptor" />
            <interceptor name="createSession" class="org.apache.struts2.interceptor.CreateSessionInterceptor" />
            <interceptor name="debugging" class="org.apache.struts2.interceptor.debugging.DebuggingInterceptor" />
            <interceptor name="execAndWait" class="org.apache.struts2.interceptor.ExecuteAndWaitInterceptor"/>
            <interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>
。。。
發現異常是這個類com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor來處理的,因此,一個最簡單的辦法,就是將該類覆寫,加上json請求的支援。
/*
 * Copyright 2002-2006,2009 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opensymphony.xwork2.interceptor;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsStatics;

import com.hatch.common.JsonHandlerException;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

/**
 * <!-- START SNIPPET: description -->
 * 
 * This interceptor forms the core functionality of the exception handling
 * feature. Exception handling allows you to map an exception to a result code,
 * just as if the action returned a result code instead of throwing an
 * unexpected exception. When an exception is encountered, it is wrapped with an
 * {@link ExceptionHolder} and pushed on the stack, providing easy access to the
 * exception from within your result.
 * 
 * <b>Note:</b> While you can configure exception mapping in your configuration
 * file at any point, the configuration will not have any effect if this
 * interceptor is not in the interceptor stack for your actions. It is
 * recommended that you make this interceptor the first interceptor on the
 * stack, ensuring that it has full access to catch any exception, even those
 * caused by other interceptors.
 * 
 * <!-- END SNIPPET: description -->
 * 
 * <p/>
 * <u>Interceptor parameters:</u>
 * 
 * <!-- START SNIPPET: parameters -->
 * 
 * <ul>
 * 
 * <li>logEnabled (optional) - Should exceptions also be logged? (boolean
 * true|false)</li>
 * 
 * <li>logLevel (optional) - what log level should we use (
 * <code>trace, debug, info, warn, error, fatal</code>)? - defaut is
 * <code>debug</code></li>
 * 
 * <li>logCategory (optional) - If provided we would use this category (eg.
 * <code>com.mycompany.app</code>). Default is to use
 * <code>com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor</code>.
 * </li>
 * 
 * </ul>
 * 
 * The parameters above enables us to log all thrown exceptions with stacktace
 * in our own logfile, and present a friendly webpage (with no stacktrace) to
 * the end user.
 * 
 * <!-- END SNIPPET: parameters -->
 * 
 * <p/>
 * <u>Extending the interceptor:</u>
 * 
 * <p/>
 * 
 * <!-- START SNIPPET: extending -->
 * 
 * If you want to add custom handling for publishing the Exception, you may
 * override
 * {@link #publishException(com.opensymphony.xwork2.ActionInvocation, ExceptionHolder)}
 * . The default implementation pushes the given ExceptionHolder on value stack.
 * A custom implementation could add additional logging etc.
 * 
 * <!-- END SNIPPET: extending -->
 * 
 * <p/>
 * <u>Example code:</u>
 * 
 * <pre>
 * <!-- START SNIPPET: example -->
 * <xwork>
 *     <package name="default" extends="xwork-default">
 *         <global-results>
 *             <result name="error" type="freemarker">error.ftl</result>
 *         </global-results>
 * 
 *         <global-exception-mappings>
 *             <exception-mapping exception="java.lang.Exception" result="error"/>
 *         </global-exception-mappings>
 * 
 *         <action name="test">
 *             <interceptor-ref name="exception"/>
 *             <interceptor-ref name="basicStack"/>
 *             <exception-mapping exception="com.acme.CustomException" result="custom_error"/>
 *             <result name="custom_error">custom_error.ftl</result>
 *             <result name="success" type="freemarker">test.ftl</result>
 *         </action>
 *     </package>
 * </xwork>
 * <!-- END SNIPPET: example -->
 * </pre>
 * 
 * <p/>
 * This second example will also log the exceptions using our own category
 * <code>com.mycompany.app.unhandled<code> at WARN level. 
 * 
 * <pre>
 * <!-- START SNIPPET: example2 -->
 * <xwork>
 *   <package name="something" extends="xwork-default">
 *      <interceptors>
 *          <interceptor-stack name="exceptionmappingStack">
 *              <interceptor-ref name="exception">
 *                  <param name="logEnabled">true</param>
 *                  <param name="logCategory">com.mycompany.app.unhandled</param>
 *                  <param name="logLevel">WARN</param>	        		
 *              </interceptor-ref>	
 *              <interceptor-ref name="i18n"/>
 *              <interceptor-ref name="staticParams"/>
 *              <interceptor-ref name="params"/>
 *              <interceptor-ref name="validation">
 *                  <param name="excludeMethods">input,back,cancel,browse</param>
 *              </interceptor-ref>
 *          </interceptor-stack>
 *      </interceptors>
 * 
 *      <default-interceptor-ref name="exceptionmappingStack"/>
 *    
 *      <global-results>
 *           <result name="unhandledException">/unhandled-exception.jsp</result>
 *      </global-results>
 * 
 *      <global-exception-mappings>
 *           <exception-mapping exception="java.lang.Exception" result="unhandledException"/>
 *      </global-exception-mappings>
 *        
 *      <action name="exceptionDemo" class="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingAction">
 *          <exception-mapping exception="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingException"
 *                             result="damm"/>
 *          <result name="input">index.jsp</result>
 *          <result name="success">success.jsp</result>            
 *          <result name="damm">damm.jsp</result>
 *      </action>
 * 
 *   </package>
 * </xwork>
 * <!-- END SNIPPET: example2 -->
 * </pre>
 * 
 * @author Matthew E. Porter (matthew dot porter at metissian dot com)
 * @author Claus Ibsen
 */
public class ExceptionMappingInterceptor1 extends AbstractInterceptor {

	protected static final Logger LOG = LoggerFactory
			.getLogger(ExceptionMappingInterceptor.class);

	protected Logger categoryLogger;
	protected boolean logEnabled = false;
	protected String logCategory;
	protected String logLevel;

	public boolean isLogEnabled() {
		return logEnabled;
	}

	public void setLogEnabled(boolean logEnabled) {
		this.logEnabled = logEnabled;
	}

	public String getLogCategory() {
		return logCategory;
	}

	public void setLogCategory(String logCatgory) {
		this.logCategory = logCatgory;
	}

	public String getLogLevel() {
		return logLevel;
	}

	public void setLogLevel(String logLevel) {
		this.logLevel = logLevel;
	}

	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		String result;

		try {
			result = invocation.invoke();
		} catch (Exception e) {
ActionContext actionContext = invocation.getInvocationContext();
			HttpServletRequest request = (HttpServletRequest) actionContext
					.get(StrutsStatics.HTTP_REQUEST);
			if (isAjaxRequest(request)) {//如果是ajax請求方式
				ValueStack stack = invocation.getStack();
				List<ExceptionMappingConfig> exceptionMappings = invocation
						.getProxy().getConfig().getExceptionMappings();
				JsonHandlerException je = new JsonHandlerException(e);
				String mappedResult = this.findResultFromExceptions(
						exceptionMappings, je);
				result = mappedResult;
				Map<String, Object> dataMap = new HashMap<String, Object>();
				stack.set("dataMap", dataMap);
				dataMap.put("result", "500");
				StringBuffer msg = new StringBuffer(e.toString()+"\n");
	            StackTraceElement[] trace = e.getStackTrace();
	            for (int i=0; i < trace.length; i++)
	            	msg.append("\tat " + trace[i]+"\n");
				ServletActionContext.getRequest().getSession().setAttribute("errMsg", msg);
			}else{// 預設處理方式
				if (isLogEnabled()) {
					handleLogging(e);
				}
				List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
				String mappedResult = this.findResultFromExceptions(exceptionMappings, e);
				if (mappedResult != null) {
					result = mappedResult;
					publishException(invocation, new ExceptionHolder(e));
				} else {
					throw e;
				}
				invocation.getStack();
				invocation.getInvocationContext().get(Action.ERROR);
//				invocation.getStack().findString("exceptionStack");
				invocation.getInvocationContext().get(Action.ERROR);
			}
		}

		return result;
	}

	private boolean isAjaxRequest(HttpServletRequest request) {
		String header = request.getHeader("X-Requested-With");
		if (header != null && "XMLHttpRequest".equals(header))
			return true;
		else
			return false;
	}

	/**
	 * Handles the logging of the exception.
	 * 
	 * @param e
	 *            the exception to log.
	 */
	protected void handleLogging(Exception e) {
		if (logCategory != null) {
			if (categoryLogger == null) {
				// init category logger
				categoryLogger = LoggerFactory.getLogger(logCategory);
			}
			doLog(categoryLogger, e);
		} else {
			doLog(LOG, e);
		}
	}

	/**
	 * Performs the actual logging.
	 * 
	 * @param logger
	 *            the provided logger to use.
	 * @param e
	 *            the exception to log.
	 */
	protected void doLog(Logger logger, Exception e) {
		if (logLevel == null) {
			logger.debug(e.getMessage(), e);
			return;
		}

		if ("trace".equalsIgnoreCase(logLevel)) {
			logger.trace(e.getMessage(), e);
		} else if ("debug".equalsIgnoreCase(logLevel)) {
			logger.debug(e.getMessage(), e);
		} else if ("info".equalsIgnoreCase(logLevel)) {
			logger.info(e.getMessage(), e);
		} else if ("warn".equalsIgnoreCase(logLevel)) {
			logger.warn(e.getMessage(), e);
		} else if ("error".equalsIgnoreCase(logLevel)) {
			logger.error(e.getMessage(), e);
		} else if ("fatal".equalsIgnoreCase(logLevel)) {
			logger.fatal(e.getMessage(), e);
		} else {
			throw new IllegalArgumentException("LogLevel [" + logLevel
					+ "] is not supported");
		}
	}

	protected String findResultFromExceptions(
			List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
		String result = null;

		// Check for specific exception mappings.
		if (exceptionMappings != null) {
			int deepest = Integer.MAX_VALUE;
			for (Object exceptionMapping : exceptionMappings) {
				ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
				int depth = getDepth(exceptionMappingConfig
						.getExceptionClassName(), t);
				if (depth >= 0 && depth < deepest) {
					deepest = depth;
					result = exceptionMappingConfig.getResult();
				}
			}
		}

		return result;
	}

	/**
	 * Return the depth to the superclass matching. 0 means ex matches exactly.
	 * Returns -1 if there's no match. Otherwise, returns depth. Lowest depth
	 * wins.
	 * 
	 * @param exceptionMapping
	 *            the mapping classname
	 * @param t
	 *            the cause
	 * @return the depth, if not found -1 is returned.
	 */
	public int getDepth(String exceptionMapping, Throwable t) {
		return getDepth(exceptionMapping, t.getClass(), 0);
	}

	private int getDepth(String exceptionMapping, Class exceptionClass,
			int depth) {
		if (exceptionClass.getName().contains(exceptionMapping)) {
			// Found it!
			return depth;
		}
		// If we've gone as far as we can go and haven't found it...
		if (exceptionClass.equals(Throwable.class)) {
			return -1;
		}
		return getDepth(exceptionMapping, exceptionClass.getSuperclass(),
				depth + 1);
	}

	/**
	 * Default implementation to handle ExceptionHolder publishing. Pushes given
	 * ExceptionHolder on the stack. Subclasses may override this to customize
	 * publishing.
	 * 
	 * @param invocation
	 *            The invocation to publish Exception for.
	 * @param exceptionHolder
	 *            The exceptionHolder wrapping the Exception to publish.
	 */
	protected void publishException(ActionInvocation invocation,
			ExceptionHolder exceptionHolder) {
		invocation.getStack().push(exceptionHolder);
	}
}
這裡說明下,如果是出現異常,而且是ajax請求的話,就找JsonHandlerException這個異常所對應的檢視:
<!-- 定義全域性檢視 -->
		<global-results>
			<result name="login" type="redirectAction">login</result>
			<result name="404">/WEB-INF/view/404.jsp</result>
			<result name="500">/WEB-INF/view/500.jsp</result>
			<result name="json_500" type="json"><param name="root">dataMap</param></result>
			<result type="json"><param name="root">dataMap</param></result><!-- ajax請求的返回檢視 -->
		</global-results>
		<!-- 定義全域性異常-->
		<global-exception-mappings>
			<exception-mapping result="500" exception="java.lang.Exception"/>
			<exception-mapping result="json_500" exception="com.hatch.common.JsonHandlerException"/>
		</global-exception-mappings>
...

下面是自定義Json異常類

package com.hatch.common;

public class JsonHandlerException extends Exception {

	/**
	 * 
	 */
	private static final long serialVersionUID = -4788951533205831941L;

	public JsonHandlerException() {
		super();
	}

	public JsonHandlerException(String message) {
		super(message);
	}

	public JsonHandlerException(String message, Throwable cause) {
		super(message, cause);
	}

	public JsonHandlerException(Throwable cause) {
		super(cause);
	}
}
這樣在頁面一端,就可以正確的獲取異常資料了。

對於普通的$.post請求,這個異常資料很容易可以人性化的提示給使用者,這裡用的easyui,可以在資料表格載入時,驗證下有沒有返回資料:

$('#dg').datagrid({
				striped : true,
				rownumbers : true,
				singleSelect : false,
				pagination : true,
				pageSize : 10,
				fitColumn : true,
				dataType : 'json',
				animate : true,
				loadMsg : '請稍候...',
		        url:'${ctx }/sys/user/doList', 
				loadFilter:function(data){
					if(!data.rows||!data.total){
						if(data.msg){
							$.messager.alert('錯誤提示','操作失敗!錯誤原因:<hr/>'+data.msg,'error');
						}else{
							$.messager.alert('錯誤提示','操作失敗!','error');
						}
						return {total:0,rows:[]};
					}
				},
		        columns:[[
						  {field:'ck',checkbox:true,align:'center'},
		                  {title:'序號',field:'id',align:'center'},
		                  {title:'登入名',field:'username',align:'center'},
		                  {title:'名稱',field:'name',align:'center'},
		                  {title:'性別',field:'gender',align:'center',formatter:function(v){ return v==1?'男':'女' }},
		                  {title:'手機',field:'phoneNumber',align:'center'},
		                  {title:'email',field:'email',align:'center'},
		                  {title:'所屬部門',field:'department',align:'center',formatter:function(v){if(v)return v['name'];}},
		                  {title:'操作',field:'Operation',align:'center',formatter: operationFormate}
		                 ]], 
		    });

以上紅色字體表示資料載入異常時,提示給使用者錯誤資訊,效果如下:

當然,如果想做一致的處理,像預設的攔截器那樣定位到一個錯誤頁面,可以在這裡加跳轉資訊:

loadFilter:function(data){
					if(!data.rows||!data.total){
						if(data.msg){
							$.messager.alert('錯誤提示','操作失敗!錯誤原因:<hr/>'+data.msg,'error');
						}else{
							$.messager.alert('錯誤提示','操作失敗!','error');
						}
						window.location = '${ctx}/jsonHandlerAction'
						return {total:0,rows:[]};
					}
				},
這樣,又能跳轉到我們預設的錯誤頁面了:

這個錯誤資訊,用了一個很笨的方法傳遞到錯誤頁面,即在攔截器捕獲到這個ajax操作的異常時 ,將這個異常資訊加入到會話中,然後在js跳轉後,在錯誤頁面中取出錯誤資訊。

至此,該方案介紹完畢,有更好方法的童鞋可以賜教!