1、實現自己的HandlerExceptionResolver
HandlerExceptionResolver是一個接口,springMVC本身已經對其有了一個自身的實現——DefaultExceptionResolver,該解析器只是對其中的一些比較典型的異常進行了攔截處理 。
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.springframework.web.servlet.HandlerExceptionResolver;
- import org.springframework.web.servlet.ModelAndView;
- public class ExceptionHandler implements HandlerExceptionResolver {
- @Override
- public ModelAndView resolveException(HttpServletRequest request,
- HttpServletResponse response, Object handler, Exception ex) {
- // TODO Auto-generated method stub
- return new ModelAndView("exception");
- }
- }
上述的resolveException的第4個參數表示對哪種類型的異常進行處理,如果想同時對多種異常進行處理,可以把它換成一個異常數組。
定義了這樣一個異常處理器之後就要在applicationContext中定義這樣一個bean對象,如:
2、使用@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;
- @Controller
- 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. 針對SpringMVC框架,修改代碼實現普通異常及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)自定義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;
- @Controller
- 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方式起了作用,整個系統的異常統一處理方式做到了統一處理。我們在開發過程中無需關心,異常處理配置了。