javascript
bean加载时调用@value时会出现空指针异常_SpringMVC全局异常处理机制
SpringMVC全局異常處理
SpringMVC除了可以做URL映射和請求攔截外,還可以做全局異常的處理。全局異常處理可能我們平時比較少機會接觸,但是每個項目都肯定會做這個處理。比如在上一間公司,是前后端分離的架構,所以后端只要有運行時異常就會報“系統異常,請稍后再試”。如果想要走上架構師的話,這個肯定是要學會的。
SpringMVC全局異常處理機制
首先,要知道全局異常處理,SpringMVC提供了兩種方式:
- 實現HandlerExceptionResolver接口,自定義異常處理器。
- 使用HandlerExceptionResolver接口的子類,也就是SpringMVC提供的異常處理器。
第一種是自定義異常處理器,第二種是SpringMVC提供的。接下來先說SpringMVC提供的幾種異常處理器的使用方式,然后再講自定義異常處理器。
SpringMVC提供的異常處理器有哪些呢?我們可以直接看源碼的類圖。
可以看出有四種:
- DefaultHandlerExceptionResolver,默認的異常處理器。根據各個不同類型的異常,返回不同的異常視圖。
- SimpleMappingExceptionResolver,簡單映射異常處理器。通過配置異常類和view的關系來解析異常。
- ResponseStatusExceptionResolver,狀態碼異常處理器。解析帶有@ResponseStatus注釋類型的異常。
- ExceptionHandlerExceptionResolver,注解形式的異常處理器。對@ExceptionHandler注解的方法進行異常解析。
DefaultHandlerExceptionResolver
這個異常處理器是SprngMVC默認的一個處理器,處理一些常見的異常,比如:沒有找到請求參數,參數類型轉換異常,請求方式不支持等等。
DefaultHandlerExceptionResolver類的doResolveException()方法:
@Override@Nullableprotected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) {try {if (ex instanceof HttpRequestMethodNotSupportedException) {return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,response, handler);}else if (ex instanceof HttpMediaTypeNotSupportedException) {return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,handler);}else if (ex instanceof HttpMediaTypeNotAcceptableException) {return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,handler);}//省略...以下還有十幾種異常的else-if}catch (Exception handlerException) {//是否打開日志,如果打開,那就記錄日志if (logger.isWarnEnabled()) {logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);}}return null;}通過if-else判斷,判斷繼承什么異常就顯示對應的錯誤碼和錯誤提示信息。由此可以知道,處理一般有兩步,一是設置響應碼,二是在響應頭設置異常信息。
MissingServletRequestPartException的處理的源碼:
protected ModelAndView handleMissingServletRequestPartException(MissingServletRequestPartException ex,HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {//設置響應碼,設置異常信息,SC_BAD_REQUEST就是400(bad request)response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());return new ModelAndView();}//響應碼public static final int SC_BAD_REQUEST = 400;為什么要存在這個異常處理器呢?
從框架的設計理念來看,這種公共的、常見的異常應該交給框架本身來完成,是一些必需處理的異常。比如參數類型轉換異常,如果程序員不處理,還有框架提供默認的處理方式,不至于出現這種錯誤而無法排查。
SimpleMappingExceptionResolver
這種異常處理器需要提前配置異常類和對應的view視圖。一般用于使用JSP的項目中,出現異常則通過這個異常處理器跳轉到指定的頁面。
怎么配置?首先搭建JSP項目我就不浪費篇幅介紹了。首先要加載一個XML文件。
@SpringBootApplication //在啟動類,加載配置文件 @ImportResource("classpath:spring-config.xml") public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);} }然后在resources目錄下,創建一個spring-config.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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><!-- 定義默認的異常處理頁面 --><property name="defaultErrorView" value="err"/><!-- 定義異常處理頁面用來獲取異常信息的變量名,默認名為exception --><property name="exceptionAttribute" value="ex"/><!-- 定義需要特殊處理的異常,用類名或完全路徑名作為key,異常也頁名作為值 --><property name="exceptionMappings"><props><!-- 數組越界異常 --><prop key="java.lang.ArrayIndexOutOfBoundsException">err/arrayIndexOutOfBounds</prop><!-- 空指針異常 --><prop key="java.lang.NullPointerException">err/nullPointer</prop></props></property></bean> </beans>然后在webapp也就是存放JSP頁面的目錄下,創建兩個JSP頁面。
arrayIndexOutOfBounds.jsp如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>數組越界異常</title> </head> <body> <h1>數組越界異常</h1> <br> <%-- 打印異常到頁面上 --%> <% Exception ex = (Exception)request.getAttribute("ex"); %> <br> <div><%= ex.getMessage() %></div> <% ex.printStackTrace(new java.io.PrintWriter(out)); %> </body> </html>nullPointer.jsp如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>空指針異常</title> </head> <body> <h1>空指針異常</h1> <br> <%-- 打印異常到頁面上 --%> <% Exception ex = (Exception)request.getAttribute("ex"); %> <br> <div><%=ex.getMessage()%></div> <% ex.printStackTrace(new java.io.PrintWriter(out)); %> </body> </html>接著創建兩個Controller,分別拋出空指針異常和數組越界異常。
@Controller @RequestMapping("/error") public class ErrController {@RequestMapping("/null")public String err() throws Exception{String str = null;//拋出空指針異常int length = str.length();System.out.println(length);return "index";}@RequestMapping("/indexOut")public String indexOut() throws Exception{int[] nums = new int[2];for (int i = 0; i < 3; i++) {//拋出數組越界異常nums[i] = i;System.out.println(nums[i]);}return "index";} }啟動項目后,我們發送兩個請求,就可以看到:
通過上述例子可以看出,其實對于現在前后端分離的項目來說,這種異常處理器已經不是很常用了。
ResponseStatusExceptionResolver
這種異常處理器主要用于處理帶有@ResponseStatus注釋的異常。下面演示一下使用方式。
首先自定義異常類繼承Exception,并且使用@ResponseStatus注解修飾。如下:
//value需要使用HttpStatus枚舉類型,HttpStatus.FORBIDDEN=403。 @ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "My defined Exception") public class DefinedException extends Exception{ }然后再在Controller層拋出此異常。如下:
@Controller @RequestMapping("/error") public class ErrController {@RequestMapping("/myException")public String ex(@RequestParam(name = "num") Integer num) throws Exception {if (num == 1) {//拋出自定義異常throw new DefinedException();}return "index";} }然后啟動項目,請求接口,可以看到如下信息:
使用這種異常處理器,需要自定義一個異常,一定要一直往上層拋出異常,如果不往上層拋出,在service或者dao層就try-catch處理掉的話,是不會觸發的。
ExceptionHandlerExceptionResolver
這個異常處理器才是最重要的,也是最常用,最靈活的,因為是使用注解。首先我們還是簡單地演示一下怎么使用:
首先需要定義一個全局的異常處理器。
//這里使用了RestControllerAdvice,是@ResponseBody和@ControllerAdvice的結合 //會把實體類轉成JSON格式的提示返回,符合前后端分離的架構 @RestControllerAdvice public class GlobalExceptionHandler {//這里自定義了一個BaseException,當拋出BaseException異常就會被此方法處理@ExceptionHandler(BaseException.class)public ErrorInfo errorHandler(HttpServletRequest req, BaseException e) throws Exception {ErrorInfo r = new ErrorInfo();r.setMessage(e.getMessage());r.setCode(ErrorInfo.ERROR);r.setUrl(req.getRequestURL().toString());return r;} }然后我們自定義一個自定義異常類BaseException:
public class BaseException extends Exception {public BaseException(String message) {super(message);} }然后在Controller層定義一個方法測試:
@Controller @RequestMapping("/error") public class ErrController {@RequestMapping("/base")public String base() throws BaseException {throw new BaseException("系統異常,請稍后重試。");} }老規矩,啟動項目,請求接口可以看到結果:
你也可以不自定義異常BaseException,而直接攔截常見的各種異常都可以。所以這是一個非常靈活的異常處理器。你也可以做跳轉頁面,返回ModelAndView即可(以免篇幅過長就不演示了,哈哈)。
小結
經過以上的演示后我們學習了SpringMVC四種異常處理器的工作機制,最后這種作為程序員我覺得是必須掌握的,前面的簡單映射異常處理器和狀態映射處理器可以選擇性掌握,默認的異常處理器了解即可。
那這么多異常處理器,究竟是如何工作的呢?為什么是設計一個接口,下面有一個抽象類加上四個實現子類呢?接下來我們通過源碼分析來揭開謎底!
源碼分析
源碼分析從哪里入手呢?在SpringMVC中,其實你想都不用想,肯定在DispatcherServlet類里。經過我順藤摸瓜,我定位在了processHandlerException()方法。怎么定位的呢?其實很簡單,看源碼:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;//異常不為空if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);//關鍵點:執行異常處理mv = processHandlerException(request, response, handler, exception);//省略...}}//省略...}processHandlerException()
就是這個直接的一個if-else判斷,那個processHandlerException()方法又是怎么處理的呢?
@Nullable protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {ModelAndView exMv = null;//判斷異常處理器的集合是否為空if (this.handlerExceptionResolvers != null) {//不為空則遍歷異常處理器 for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {//調用異常處理器的resolveException()方法進行處理異常exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);//判斷返回的ModelAndView是否為null,不為null則跳出循環,為null則繼續下一個異常處理器if (exMv != null) {break;}}}//如果ModelAndView不為空if (exMv != null) {if (exMv.isEmpty()) {//設置異常信息提示request.setAttribute(EXCEPTION_ATTRIBUTE, ex);return null;}//如果返回的ModelAndView不包含viewif (!exMv.hasView()) {//設置一個默認的視圖 String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}//省略...//返回異常的ModelAndView return exMv;}throw ex; }這不就是責任鏈模式嗎!提前加載異常處理器到handlerExceptionResolvers集合中,然后遍歷去執行,能處理就處理,不能處理就跳到下一個異常處理器處理。
那接下來我們就有一個問題了,handlerExceptionResolvers集合是怎么加載異常處理器的?這個問題很簡單,就是使用DispatcherServlet.properties配置文件。這個文件真的很重要!!!
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver默認是加載以上三種異常處理器到集合中,所以只要帶有@ControllerAdvice、@ExceptionHandler、@ResponseStatus注解的都會被掃描。SimpleMappingExceptionResolver則是通過xml文件(當然也可以使用@Configuration)去配置。
resolveException()
其實在resolveException()處理異常的方法中,還使用了模板模式。
@Override@Nullablepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) {//省略...//預處理prepareResponse(ex, response);//調用了一個抽象方法,抽象方法由子類去實現ModelAndView result = doResolveException(request, response, handler, ex);//省略...}抽象方法doResolveException(),由子類實現。
@Nullable protected abstract ModelAndView doResolveException(HttpServletRequest request,HttpServletResponse response, @Nullable Object handler, Exception ex);怎么識別模板方法,其實很簡單,只要看到抽象類,有個具體方法里面調用了抽象方法,那很大可能就是模板模式。抽象方法就是模板方法,由子類實現。
子類我們都知道就是那四個異常處理器實現類了。
總結
用流程圖概括一下:
經過以上的學習后,我們知道只需要把異常處理器加到集合中,就可以執行。所以我們可以使用直接實現HandlerExceptionResolver接口的方式來實現異常處理器。
實現HandlerExceptionResolver接口實現全局異常處理
首先自定一個異常類MyException。
public class MyException extends Exception {public MyException(String message) {super(message);} }然后實現HandlerExceptionResolver接口定義一個異常處理器。
//注冊異常處理器到Spring容器中 @Component public class MyExceptionHandler implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {try {//如果屬于MyException異常,則輸出異常提示到頁面if (ex instanceof MyException) {response.setContentType("text/html;charset=utf-8");response.getWriter().println(ex.getMessage());//這里返回null,不做處理。也可以返回ModelAndView跳轉頁面return null;}} catch (IOException e) {e.printStackTrace();}return null;} }然后在Controller層定義一個方法測試:
@Controller @RequestMapping("/error") public class ErrController {@RequestMapping("/myEx")public String myEx() throws MyException {System.out.println("執行myEx()");throw new MyException("自定義異常提示信息");} }啟動項目,請求接口,我們可以看到:
作者:Java技術愛好者鏈接:SpringMVC全局異常處理機制 - 云+社區 - 騰訊云
來源:騰訊云社區
總結
以上是生活随笔為你收集整理的bean加载时调用@value时会出现空指针异常_SpringMVC全局异常处理机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么把异地车开回来?
- 下一篇: 上级对下级用通知合适吗_用报纸练书法,真