SpringMVC入門學習(十六)----全局異常處理


1、SpringMVC全局異常處理的四種方式

在項目上線之后,往往會出現一些不可預料的異常信息,對於邏輯性或設計性問題,開發人員或者維護人員需要通過日志,查看異常信息並排除異常;而對於用戶,則需要為其呈現出其可以理解的異常提示頁面,讓用戶有一個良好的使用體驗。所以異常的處理對於一個Web項目來說是非常重要的。Spring MVC提供了強大的異常處理機制。

SpringMVC提供的異常處理主要有以下四種方式:

  • 使用 Spring MVC 提供的簡單異常處理器 SimpleMappingExceptionResolver
  • 實現異常處理接口 HandlerExceptionResolver
  • 使用 @ExceptionHandler 注解實現異常處理
  • 使用 @ControllerAdvice + @ExceptionHandler注解

注意:如果XML中也配置了相同的映射關系,那么SpringMVC會優先采納基於注解的映射

在SpringMVC中處理異常的推薦方式:使用 @ControllerAdvice + @ExceptionHandler 注解實現全局異常處理。

2、通過SimpleMappingExceptionResolver實現

SimpleMappingExceptionResolver異常處理器是SpringMVC定義好的異常處理器。使用 SimpleMappingExceptionResolver 進行異常處理的優缺點:

  • 優點:集成簡單、有良好的擴展性、對已有代碼沒有入侵性等
  • 缺點:該方法僅能獲取到異常信息,若在出現異常時,對需要獲取除異常以外的數據的情況不適用。

下面在SpringMVC的XML文件中配置 SimpleMappingExceptionResolver 對象,配置如下。

<!-- 配置異常映射 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="exceptionResolver">
    <!-- 指定默認的異常響應頁面。若發生的異常不是exceptionMappings中指定的異常,則使用默認異常響應頁面。 -->
    <property name="defaultErrorView" value="error"></property>
    <!-- exceptionAttribute屬性:設置將異常對象存入請求域時使用的屬性名 -->
    <!-- 如果沒有配置這個屬性,那么默認使用"exception"作為屬性名。源碼中文檔說明如下:
           * Set the name of the model attribute as which the exception should be exposed. Default is "exception". -->
    <property name="exceptionAttribute" value="exception"></property>
    <!-- 用於指定具體的不同類型的異常所對應的異常響應頁面。 -->
    <property name="exceptionMappings">
        <props>
            <!-- key屬性:指定異常類型 -->
            <!-- 文本標簽體:指定和異常對應的邏輯視圖名稱 -->
            <prop key="java.lang.ArithmeticException">show-message</prop>
            <prop key="java.lang.RuntimeException">show-runtime-message</prop>
            <prop key="java.lang.Exception">show-exception-message</prop>
        </props>
    </property>
</bean>

上面配置了三個異常類型,那么它的匹配規則是什么呢?是從最大的異常開始,還是精確匹配呢?

  • 匹配規則1:如果異常對象能夠在映射關系中找到精確匹配的規則,那么就執行這個精確匹配的規則
  • 匹配規則2:如果異常對象能夠在映射關系中找到多個匹配的規則,優先采納精確匹配的規則
  • 匹配規則3:如果異常對象能夠在映射關系中找到多個匹配的規則,且沒有精確匹配的,那么會采納范圍更接近的那個
  • 匹配規則4:如果注解中也配置了相同的映射關系,那么SpringMVC會優先采納基於注解的映射

結論:在整個SpringMVC全局異常處理中,當異常發生時,會優先采取精確匹配的規則,沒有的話會采納范圍更接近的那個,其它的同理。


下面創建模擬出現異常的Controller方法:

@RequestMapping(value = "/exception")
public String exceptionHandler(){
    // 模擬出現異常
    System.out.println(10 / 0);
    return "success";
}

用於展示異常信息的頁面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>異常信息頁面</title>
</head>
<body>
    <h1>系統信息</h1>
    異常對象:${requestScope.exception}<br/>
    異常消息:${requestScope.exception.message}<br/>
</body>
</html>

測試的結果如下圖所示:

image

3、通過實現HandlerExceptionResolver接口

上面使用的是Spring MVC定義好的SimpleMappingExceptionResolver異常處理器,可以實現發生指定異常后跳轉到指定的頁面。但若要實現在捕獲到指定異常時,執行一些額外操作它是完成不了的。此時,就需要自定義異常處理器,需要使用到HandlerExceptionResolver接口。

首先新建一個自定義異常類 CustomException:

/**
 * 自定義異常
 */
public class CustomException extends Exception{

    private String message;
    
    public CustomException(String message) {
        super(message);
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}

然后創建一個實現 HandlerExceptionResolver 接口的實現類,並且實現其唯一的方法resolveException(),這種方式可以進行全局的異常處理。

/**
 * 異常處理,通過實現HandlerExceptionResolver接口來實現
 */
@Component
public class GlobalException implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                         HttpServletResponse httpServletResponse,
                                         Object o, Exception exception) {
        System.out.println(exception.getMessage());
        CustomException customException = null;

        //解析出異常類型,如果該異常類型是自定義的異常,則直接取出異常信息,否則用自定義異常輸出一下
        if (exception instanceof CustomException){
            customException = (CustomException) exception;
        }else {
            customException = new CustomException("出現了未知的錯誤!!!");
        }
        // 獲取錯誤信息
        String message = customException.getMessage();
        System.out.println(message);
        System.out.println("---------");
        // 將錯誤信息帶到頁面輸出
        ModelAndView mv = new ModelAndView();
        mv.addObject("exception",customException);
        mv.setViewName("show-exception-message");
        return mv;
    }
}

resolveException方法的參數“Exception e”即為Controller或其下層拋出的異常。參數“Object o”就是處理器適配器要執行的Handler對象。resolveException方法的返回值類型是ModelAndView,也就是說,可以通過這個返回值類設置發出異常時顯示的頁面。

4、使用 @ExceptionHandler注解

@ExceptionHandler注解用來將一個方法標注為異常處理方法。該注解中只有一個可選的屬性value,是一個Class<?>數組,用於指定該注解的方法所要處理的異常類,即所要匹配的異常。被該注解修飾的方法的返回值為異常處理后的跳轉頁面,其返回值可以是ModelAndView、String,或void;方法名隨意,方法的參數可以是 Exception 及其子類對象、Model、HttpServletRequest、HttpServletResponse 等。系統會自動為這些方法參數賦值。

@ExceptionHandler注解處理異常的作用域:單個類,只針對當前Controller。

/**
 * 基於注解的異常處理
 */
@Controller
public class ExceptionController {
    @RequestMapping(value = "/exception1")
    public String exception1() {
        // 模擬出現異常
        System.out.println(10 / 0);
        return "success";
    }

    @RequestMapping(value = "/exception2")
    public void exception2() throws CustomException {
        // 模擬出現異常
        throw new CustomException("我拋出了一個異常!!!");
    }

    //處理自定義異常
    @ExceptionHandler({CustomException.class, ArithmeticException.class})
    public String exceptionHandler1(Exception e, Model model) {
        // 打印錯誤信息
        System.out.println(e.getMessage());
        e.printStackTrace();
        // 將錯誤數據存入請求域
        model.addAttribute("exception", e);
        return "show-annotation-message";
    }
}

上面的代碼運行結果:

image

注意:如果在Controller中單獨使用這個注解是有缺陷的,就是不能夠全局處理異常,因為進行異常處理的方法必須與出錯的方法在同一個Controller里面,也就是說每個Controller類中都要寫一遍,所以實用性不高。

解決方案:可以將處理異常的信息抽取出來放在一個BaseController,然后對需要處理異常的Controller繼承該類即可。

public class BaseController {
    //處理自定義異常
    @ExceptionHandler({CustomException.class, ArithmeticException.class})
    public String exceptionHandler1(Exception e, Model model) {
        // 打印錯誤信息
        System.out.println(e.getMessage());
        e.printStackTrace();
        // 將錯誤數據存入請求域
        model.addAttribute("exception", e);
        return "show-annotation-message";
    }
}

但是還是存在同樣的問題,每個類都得繼承它,可見這種方式同樣不可取,所以一般使用下面這種方式:@ControllerAdvice和@ ExceptionHandle 注解配合使用。

5、用 @ControllerAdvice+@ ExceptionHandler注解(推薦)

上面說到 @ExceptionHandler注解標注的異常處理方法必須與出錯的方法在同一個Controller里面,所以這種方式是只對應單個Controller類。那么此時有一種更好的解決方案:可以使用@ControllerAdvice+@ExceptionHandler注解來解決,這個是 Spring 3.2 帶來的新特性。

兩者一起使用的作用域:全局異常處理,針對全部Controller中的指定異常類

@ControllerAdvice和@ ExceptionHandler 這兩個注解配合使用的代碼如下:

/**
 * 基於注解的異常處理  @ControllerAdvice+@ExceptionHandler
 */
// 這個注解表示當前類是一個異常映射類
@ControllerAdvice
public class MyException {

    // 在@ExceptionHandler注解中指定異常類型
    @ExceptionHandler(value = {CustomException.class, ArithmeticException.class})
    public ModelAndView exceptionMapping(Exception exception) {// 方法形參位置接收SpringMVC捕獲到的異常對象

        // 可以將異常對象存入模型;將展示異常信息的視圖設置為邏輯視圖名稱
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("exception", exception);
        modelAndView.setViewName("show-annotation-message");
        // 打印一下信息
        System.out.println(exception.getMessage());
        return modelAndView;
    }
}

注:@ControllerAdvice 注解的內部是使用@Component 注解修飾的,可以點進源碼查看運行:

image

@Component,@Service,@Controller,@Repository注解修飾的類,就是把這個類的對象交由Spring IOC容器來管理,相當於配置文件中的 <bean id="" class=""/>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM