SpringMVC 提供的異常處理主要有兩種方式,一種是直接實現本身的HandlerExceptionResolver,另外一種是使用註解的方式實現一個專門用於處理異 常的Controller——ExceptionHandler。前者當發生異常時,頁面會跳到指定的錯誤頁面,後者一樣,只是後者會在每一個 controller中都須要加入重複的代碼。如何進行簡單地統一配置異常,使得發生普通錯誤指定到固定的頁面,ajax發生錯直接經過js獲取,展示給 用戶,變得很是重要。下面先介紹下2種異常處理方式,同時,結合現有的代碼,讓其支持ajax方式,實現spring MVC web系統的異常統一處理。javascript
一、實現本身的HandlerExceptionResolver,HandlerExceptionResolver是一個接 口,springMVC自己已經對其有了一個自身的實現——DefaultExceptionResolver,該解析器只是對其中的一些比較典型的異常 進行了攔截處理 。css
import javax.servlet.http.HttpServletRequest; java
import javax.servlet.http.HttpServletResponse; web
import org.springframework.web.servlet.HandlerExceptionResolver; ajax
import org.springframework.web.servlet.ModelAndView; spring
public class ExceptionHandler implements HandlerExceptionResolver { apache
@Override json
public ModelAndView resolveException(HttpServletRequest request, 數組
HttpServletResponse response, Object handler, Exception ex) {
// TODO Auto-generated method stub
return new ModelAndView("exception");
}
}
上述的resolveException的第4個參數表示對哪一種類型的異常進行處理,若是想同時對多種異常進行處理,能夠把它換成一個異常數組。
定義了這樣一個異常處理器以後就要在applicationContext中定義這樣一個bean對象,如:
<bean id="exceptionResolver" class="com.tiantian.xxx.web.handler.ExceptionHandler"/>
二、使用@ExceptionHandler進行處理
使用@ExceptionHandler進行處理有一個很差的地方是進行異常處理的方法必須與出錯的方法在同一個Controller裏面
如:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import com.tiantian.blog.web.servlet.MyException;
public class GlobalController {
/**
* 用於處理異常的
* @return
*/
@ExceptionHandler({MyException.class})
public String exception(MyException e) {
System.out.println(e.getMessage());
e.printStackTrace();
return "exception";
}
@RequestMapping("test")
public void test() {
throw new MyException("出錯了!");
}
}
這裏在頁面上訪問test方法的時候就會報錯,而擁有該test方法的Controller又擁有一個處理該異常的方法,這個時候處理異常的方法就會被調用。當發生異常的時候,上述兩種方式都使用了的時候,第一種方式會將第二種方式覆蓋。
3. 針對Spring MVC 框架,修改代碼實現普通異常及ajax異常的所有統一處理解決方案。
在上篇文章中,關於spring異常框架體系講的很是清楚,Dao層,以及sevcie層異常咱們創建以下異常。
package com.jason.exception;
public class BusinessException extends Exception {
private static final long serialVersionUID = 1L;
public BusinessException() {
// TODO Auto-generated constructor stub
}
public BusinessException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public BusinessException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
}
package com.jason.exception;
public class SystemException extends RuntimeException {
private static final long serialVersionUID = 1L;
public SystemException() {
// TODO Auto-generated constructor stub
}
/**
* @param message
*/
public SystemException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
/**
* @param cause
*/
public SystemException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
/**
* @param message
* @param cause
*/
public SystemException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
}
在sevice層咱們須要將創建的異常拋出,在controller層,咱們須要捕捉異常,將其轉換直接拋出,拋出的異常,但願能經過咱們本身統一的配置,支持普通頁面和ajax方式的頁面處理,下面就詳細講一下步驟。
(1) 配置web.xml 文件,將經常使用的異常進行配置,配置文件以下403,404,405,500頁面都配置好了:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>SpringJSON</display-name>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>SpringJSON.webapp.root</param-value>
</context-param>
<!--******************************** -->
<!--*******log4j日誌信息的配置,設置在classpath根目錄下 ,spring中不少代碼使用了不一樣的日誌接口,
既有log4j也有commons-logging,這裏只是強制轉換爲log4j!而且,log4j的配置文件只能放在classpath根路徑。
同時,須要經過commons-logging配置將日誌控制權轉交給log4j。同時commons-logging.properties必須放置
在classpath根路徑****** -->
<!--******************************* -->
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.xml</param-value>
</context-param>
<!--Spring默認刷新Log4j配置文件的間隔,單位爲millisecond,能夠不設置 -->
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>
<!--******************************** -->
<!--*******spring bean的配置******** -->
<!--applicationContext.xml用於對應用層面作總體控制。按照分層思想,
統領service層,dao層,datasource層,及國際化層-->
<!--******************************* -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!--******************************** -->
<!--*******字符集 過濾器************ -->
<!--******************************* -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring 分發器,設置MVC配置信息 -->
<servlet>
<servlet-name>SpringJSON</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!--******************************** -->
<!--***使用.html後綴,一方面用戶不能經過URL知道咱們採用何種服務端技術,
同時,可騙過搜索引擎,增長被收錄的機率 。真正的靜態網頁能夠用.htm,以免被框架攔截-->
<!--******************************* -->
<servlet-mapping>
<servlet-name>SpringJSON</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<error-page>
<error-code>403</error-code>
<location>/WEB-INF/pages/error/403.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/pages/error/404.jsp</location>
</error-page>
<error-page>
<error-code>405</error-code>
<location>/WEB-INF/pages/error/405.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/pages/error/500.jsp</location>
</error-page>
</web-app>
2.創建相應的error頁面,其中errorpage.jsp 是業務異常界面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isErrorPage="true"%>
<%@ include file="/common/taglibs.jsp"%>
<!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>
<title>error page</title>
<script type="text/javascript">
$(function(){
$("#center-div").center(true);
})
</script>
</head>
<body style="margin: 0;padding: 0;background-color: #f5f5f5;">
<div id="center-div">
<table style="height: 100%; width: 600px; text-align: center;">
<tr>
<td>
<img width="220" height="393" src="${basePath}/images/common/error.png" style="float: left; padding-right: 20px;" alt="" />
<%= exception.getMessage()%>
<p style="line-height: 12px; color: #666666; font-family: Tahoma, '宋體'; font-size: 12px; text-align: left;">
<a href="javascript:history.go(-1);">返回</a>!!!
</p>
</td>
</tr>
</table>
</div>
</body>
</html>
errorpage.jsp代碼內容以下:
3.分析spring源碼,自定義SimpleMappingExceptionResolver覆蓋spring的SimpleMappingExceptionResolver。
關於SimpleMappingExceptionResolver的用法,你們都知道,只需在application-servlet.xml中作以下的配置
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="com.jason.exception.SystemException">error/500</prop>
<prop key="com.jason.exception.BusinessException">error/errorpage</prop>
<prop key="java.lang.exception">error/500</prop>
</props>
</property>
</bean>
觀察SimpleMappingExceptionResolver,咱們能夠複寫其doResolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)方法,經過修改該方法實現普通異常和ajax異常的處理,代碼以下:
package com.jason.exception;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
public class CustomSimpleMappingExceptionResolver extends
SimpleMappingExceptionResolver {
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
// Expose ModelAndView for chosen error view.
String viewName = determineViewName(ex, request);
if (viewName != null) {// JSP格式返回
if (!(request.getHeader("accept").indexOf("application/json") > -1 || (request
.getHeader("X-Requested-With")!= null && request
.getHeader("X-Requested-With").indexOf("XMLHttpRequest") > -1))) {
// 若是不是異步請求
// Apply HTTP status code for error views, if specified.
// Only apply it if we're processing a top-level request.
Integer statusCode = determineStatusCode(request, viewName);
if (statusCode != null) {
applyStatusCodeIfPossible(request, response, statusCode);
}
return getModelAndView(viewName, ex, request);
} else {// JSON格式返回
try {
PrintWriter writer = response.getWriter();
writer.write(ex.getMessage());
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
} else {
return null;
}
}
}
配置application-servelt.xml以下:(代碼是在大的工程中提煉出來的,具體有些東西這裏不作處理)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置靜態資源,直接映射到對應的文件夾,不被DispatcherServlet處理 -->
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/html/**" location="/html/"/>
<mvc:resources mapping="/common/**" location="/common/"/>
<!-- Configures the @Controller programming model -->
<mvc:annotation-driven />
<!--掃描web包,應用Spring的註解-->
<context:component-scan base-package="com.jason.web"/>
<bean id="captchaProducer" name= "captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
<property name="config">
<bean class="com.google.code.kaptcha.util.Config">
<constructor-arg>
<props>
<prop key="kaptcha.image.width">300</prop>
<prop key="kaptcha.image.height">60</prop>
<prop key="kaptcha.textproducer.char.string">0123456789</prop>
<prop key="kaptcha.textproducer.char.length">4</prop>
</props>
</constructor-arg>
</bean>
</property>
</bean>
<!--
<bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
<property name="config">
<bean class="com.google.code.kaptcha.util.Config">
<constructor-arg>
<props>
<prop key="kaptcha.border">no</prop>
<prop key="kaptcha.border.color">105,179,90</prop>
<prop key="kaptcha.textproducer.font.color">red</prop>
<prop key="kaptcha.image.width">250</prop>
<prop key="kaptcha.textproducer.font.size">90</prop>
<prop key="kaptcha.image.height">90</prop>
<prop key="kaptcha.session.key">code</prop>
<prop key="kaptcha.textproducer.char.length">4</prop>
<prop key="kaptcha.textproducer.font.names">宋體,楷體,微軟雅黑</prop>
</props>
</constructor-arg>
</bean>
</property>
</bean>
-->
<!--
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
-->
<bean id="exceptionResolver" class="com.jason.exception.CustomSimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="com.jason.exception.SystemException">error/500</prop>
<prop key="com.jason.exception.BusinessException">error/errorpage</prop>
<prop key="java.lang.exception">error/500</prop>
</props>
</property>
</bean>
<!--啓動Spring MVC的註解功能,設置編碼方式,防止亂碼-->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name = "supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<!--對模型視圖名稱的解析,即在模型視圖名稱添加先後綴InternalResourceViewResolver-->
<!--默認的就是JstlView因此這裏就不用配置viewClass -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/pages/"
p:suffix=".jsp" />
</beans>
至此,整個異常體系架構配置成功,當整個工程出現異常時,頁面會根據web.xml跳轉到指定的頁面。當在系統應用中出現普通異常時,根據是系統異 常仍是應用異常,跳到相應的界面,當ajax異常時,在ajax的error中可直接得到異常。普通的異常咱們都配置好了界面,系統會自動跳轉,主要看一 下ajax的方式。
具體演示以下:
在登陸界面創建以下的controller
package com.jason.web;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.jason.domain.User;
import com.jason.exception.BusinessException;
import com.jason.service.UserService;
import com.jason.util.Constants;
import com.jason.web.dto.LoginCommand;
public class LoginController {
@Autowired
private UserService userService;
/**
* jump into the login page
*
* @return
* @throws BusinessException
* @throws
* @throws BusinessException
*/
@RequestMapping(value = "/index.html")
public String loginPage() throws BusinessException {
return Constants.LOGIN_PAGE;
}
/**
* get the json object
*
* @return
* @throws Exception
*/
@RequestMapping(value = "/josontest.html")
public @ResponseBody
Map<String, Object> getjson() throws BusinessException {
Map<String, Object> map = new HashMap<String, Object>();
try {
map.put("content", "123");
map.put("result", true);
map.put("account", 1);
throw new Exception();
} catch (Exception e) {
throw new BusinessException("detail of ajax exception information");
}
}
/**
* login in operation
*
* @param request
* @param loginCommand
* @return
* @throws IOException
*/
@RequestMapping(value = "/login.html")
public ModelAndView loginIn(HttpServletRequest request,
HttpServletResponse respone, LoginCommand loginCommand)
throws IOException {
boolean isValidUser = userService.hasMatchUser(
loginCommand.getUserName(), loginCommand.getPassword());
boolean isValidateCaptcha = validateCaptcha(request, loginCommand);
ModelAndView modeview = new ModelAndView(Constants.LOGIN_PAGE);
if (!isValidUser) {
// if have more information,you can put a map to modelView,this use
// internalization
modeview.addObject("loginError", "login.user.error");
return modeview;
} else if (!isValidateCaptcha) {
// if have more information,you can put a map to modelView,this use
// internalization
modeview.addObject("loginError", "login.user.kaptchaError");
return modeview;
} else {
User user = userService.findUserByUserName(loginCommand
.getUserName());
user.setLastIp(request.getLocalAddr());
user.setLastVisit(new Date());
userService.loginSuccess(user);
// we can also use
request.getSession().setAttribute(Constants.LOGINED, user);
String uri = (String) request.getSession().getAttribute(
Constants.CURRENTPAGE);
if (uri != null
&& !StringUtils.equalsIgnoreCase(uri,
Constants.CAPTCHA_IMAGE)) {
respone.sendRedirect(request.getContextPath() + uri);
}
return new ModelAndView(Constants.FRONT_MAIN_PAGE);
}
}
/**
* logout operation
*
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/logout.html")
public ModelAndView logout(HttpServletRequest request,
HttpServletResponse response) {
/*
* HttpServletRequest.getSession(ture) equals to
* HttpServletRequest.getSession() means a new session created if no
* session exists request.getSession(false) means if session exists get
* the session,or value null
*/
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
return new ModelAndView("redirect:/index.jsp");
}
/**
* check the Captcha code
*
* @param request
* @param command
* @return
*/
protected Boolean validateCaptcha(HttpServletRequest request, Object command) {
String captchaId = (String) request.getSession().getAttribute(
com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
String response = ((LoginCommand) command).getKaptchaCode();
if (!StringUtils.equalsIgnoreCase(captchaId, response)) {
return false;
}
return true;
}
}
首先看一下ajax的方式,在controller中咱們認爲讓ajax拋出同樣,在頁面中咱們採用js這樣調用
function ajaxTest()
{
$.ajax( {
type : 'GET',
//contentType : 'application/json',
url : '${basePath}/josontest.html',
async: false,//禁止ajax的異步操做,使之順序執行。
dataType : 'json',
success : function(data,textStatus){
alert(JSON.stringify(data));
},
error : function(data,textstatus){
alert(data.responseText);
}
});
}
當拋出異常是,咱們在js的error中採用 alert(data.responseText);將錯誤信息彈出,展示給用戶,具體頁面代碼以下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="/common/taglibs.jsp"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>spring login information</title>
<script type="text/javascript">
function ajaxTest()
{
$.ajax( {
type : 'GET',
//contentType : 'application/json',
url : '${basePath}/josontest.html',
async: false,//禁止ajax的異步操做,使之順序執行。
dataType : 'json',
success : function(data,textStatus){
alert(JSON.stringify(data));
},
error : function(data,textstatus){
alert(data.responseText);
}
});
}
</script>
</head>
<body>
<table cellpadding="0" cellspacing="0" style="width:100%;">
<tr>
<td rowspan="2" style="width:30px;">
</td>
<td style="height:72px;">
<div>
spring login front information
</div>
<div>
${loginedUser.userName},歡迎您進入Spring login information,您當前積分爲${loginedUser.credits};
</div>
<div>
<a href="${basePath}/backendmain.html">後臺管理</a>
</div>
</td>
<td style="height:72px;">
<div>
<input type=button value="Ajax Exception Test" onclick="ajaxTest();"></input>
</div>
</td>
<td>
<div>
<a href="${basePath}/logout.html">退出</a>
</div>
</td>
</tr>
</table>
</body>
</html>
驗證效果:
至此,ajax方式起了做用,整個系統的異常統一處理方式作到了統一處理。咱們在開發過程當中無需關心,異常處理配置了。