Java 及 Springboot 2 中的異常閒談

Java及springboot2中的異常閒談

異常的簡介

首先看一下常見的 Throwable 類html

官方Java 8 Throwable 官方介紹。 Throwable 類是 Java 語言中全部錯誤或異常的超類。只有當對象是此類(或其子類之一)的實例時,才能經過 Java 虛擬機或者 Java throw 語句拋出。相似地,只有此類或其子類之一才能夠是 catch 子句中的參數類型。出於對異常的編譯時檢查的目的,Throwable 和任何沒有繼承自 RuntimeException 或者 Error 的 Throwable 子類,那麼則視爲檢查異常 (For the purposes of compile-time checking of exceptions, Throwable and any subclass of Throwable that is not also a subclass of either RuntimeException or Error are regarded as checked exceptions.) 兩個子類的實例,Error 和 Exception,一般用於指示發生了異常狀況。一般,這些實例是在異常狀況的上下文中新近建立的,所以包含了相關的信息(好比堆棧跟蹤數據)。java

Error 是 Throwable 的子類,用於指示合理的應用程序不該該試圖捕獲的嚴重問題。大多數這樣的錯誤都是異常條件。雖然 ThreadDeath 錯誤是一個「正規」的條件,但它也是 Error 的子類,由於大多數應用程序都不該該試圖捕獲它。 在執行該方法期間,無需在其 throws 子句中聲明可能拋出可是未能捕獲的 Error 的任何子類,由於這些錯誤多是不再會發生的異常條件。web

Exception 類及其子類是 Throwable 的一種形式,它指出了合理的應用程序想要捕獲的條件。spring

所以咱們知道在 Java 語言中,首先能夠被拋出(throw)和捕獲(cache)的是 Throwable 或 Throwable 的子類。Throwable 有兩個直接子類 Error 和 Exception。 Error 是程序不該該去主動捕獲的嚴重問題,Exception 是能夠去捕獲的。Throwable 和任何沒有繼承自 RuntimeException 或者 Error 的 Throwable 子類,都是編譯期異常。一般分爲編譯期異常和運行期異常,運行期異常一般指 RuntimeException 及其子類。編譯期異常須要顯示處理,運行期異常不須要顯示處理。異常如果不捕獲致使的結果是線程的死亡。sql

Java 中異常的處理

舉一個獲取 InputStream 的例子,沒什麼業務,異常處理大多跟業務相關。這裏暫時僅從語法角度來演示一下。json

  • 編譯期異常api

    • try-cache
    public InputStream selfHandlerException(){
        InputStream inputStream = null;
        try {
            // 作業務
            inputStream = new FileInputStream("");
        } catch (FileNotFoundException e) {
            // 處理異常
            e.printStackTrace();
        } finally {
            // 有沒有異常都乾的工做
        }
        return inputStream;
    }
    • throws 上級,交由上級處理
    public InputStream callHandlerException() throws FileNotFoundException {
        InputStream inputStream = new FileInputStream("");
        return inputStream;
    }
  • 運行期異常,能夠不顯示處理springboot

    public void runTime() {
      throws new RuntimeException();
    }
  • 針對許多時候,咱們方法中須要保證在執行某段代碼後確保會執行另外一段代碼,而不能處理異常oracle

    // 即相似這樣的代碼在 spring 源碼中時長見到
    try {
        // 執行某段代碼
        System.out.println("try up");
        int a = 1/0;
        System.out.println("try down");
    }catch (Exception e){
        System.out.println("catch");
        throw e;
    }finally {
        // 確保會執行的代碼
        System.out.println("finally");
    }
    // 咱們能夠寫這樣的代碼來處理
    try {
        // 執行某段代碼
        System.out.println("try up");
        int a = 1/0;
        System.out.println("try down");
    }finally {
        // 確保會執行的代碼
        System.out.println("finally");
    }

    而這時問題來了,try finally 能捕獲哪些異常呢?我找了下資料沒有找到,使用 jd-gui 反編譯不回來。我嘗試在這裏寫了 throw new Throwable(""),這時會報編譯異常,因此這裏捕獲的不是 Throwable,嘗試了 Throwable 的兩個直接子類 Error 和 Exceptin 是能夠的。至於爲何這麼設計還沒找到相關資料,我想應該是爲了留出之後再定義的 Throwable 子類在此語句中須要額外處理吧。app

  • 更詳細的異常信息,官網異常處理

spring boot 2 web(spring MVC) 中的異常處理

  • Spring boot 中默認處理方式

    spring boot 官方文檔 在 27.1.11 Error Handling 章節講到,默認狀況下,Spring Boot提供了一個 /error 映射處理全部的錯誤,並將其註冊爲servlet容器中的「全局」錯誤頁面。 BasicErrorController 做爲默認的響應處理程序。

    在源碼中即:經過 ErrorPageRegistrarBeanPostProcessor 的 postProcessBeforeInitialization(ErrorPageRegistry registry) 方法進行錯誤頁註冊;默認使用的是 Tomcat,經過 TomcatServletWebServerFactory 的 configureContext(Context context, ServletContextInitializer[] initializers) 方法進行配置。 BasicErrorController 提供了以下兩個方法來處理錯誤信息。對於請求 accept-type=text/html 則使用第一個方法,不然使用第二個方法處理。

    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
    		HttpServletResponse response) {
    	HttpStatus status = getStatus(request);
    	Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
    			request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
    	response.setStatus(status.value());
    	ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    	return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }
    
    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    	Map<String, Object> body = getErrorAttributes(request,
    			isIncludeStackTrace(request, MediaType.ALL));
    	HttpStatus status = getStatus(request);
    	return new ResponseEntity<>(body, status);
    }

    第一個方法默認返回一個註明,Httpcode 等信息。 第二個方法默認返回的 Json 數據內容大體以下:

    {
        "timestamp": 1520403098597,
        "status": 500,
        "error": "Internal Server Error",
        "exception": "java.lang.ArithmeticException",
        "message": "/ by zero",
        "path": "/demos/"
    }
  • 能夠選擇新增處理方式

    若是默認返回的信息不能知足咱們的需求,咱們能夠採用如下幾種方式定義本身的處理規則及返回數據。

    • ErrorController 接口
      • implements ErrorController ,徹底重寫框架中的錯誤處理方式。
      • extends BasicErrorController , @RequestMapping(produces = "text/html") 添加不一樣的 produces,來擴充實現。
    • HandlerExceptionResolver 接口
      • 針對特定異常能夠定義一個使用 @ControllerAdvice 註解的類,使用 @ExceptionHandler(YourException.class) 指明具體的異常類型。
        // basePackageClasses = AcmeController.class 能夠省略不寫,默認全局
          @ControllerAdvice(basePackageClasses = AcmeController.class)
          public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
        
            // 指明具體的異常,若不指明則默認所有
          	@ExceptionHandler(YourException.class)
          	@ResponseBody
          	ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
          		HttpStatus status = getStatus(request);
          		return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
          	}
        
          	private HttpStatus getStatus(HttpServletRequest request) {
          		Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
          		if (statusCode == null) {
          			return HttpStatus.INTERNAL_SERVER_ERROR;
          		}
          		return HttpStatus.valueOf(statusCode);
          	}
        
          }
        • 實現 HandlerExceptionResolver 來處理
        @Configuration
          public class GlobHandlerExceptionResolver implements HandlerExceptionResolver {
              @Override
              public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                  return new ModelAndView();
              }
          }
    • 註冊 Filter 來處理
      import org.springframework.boot.web.servlet.FilterRegistrationBean;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
      
        import javax.servlet.*;
        import java.io.IOException;
        import java.util.EnumSet;
      
        @Configuration
        public class ExceptionConfig {
      
            @Bean
            public FilterRegistrationBean myFilter() {
                FilterRegistrationBean registration = new FilterRegistrationBean();
                registration.setFilter(new MyFilter());
                registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
                return registration;
            }
        }
        class MyFilter implements Filter {
      
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
      
            }
      
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                try {
                    chain.doFilter(request, response);
                    // 這裏能夠捕獲異常來處理
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
      
            @Override
            public void destroy() {
      
            }
        }
  • 建議方案

    建議使用 @ControllerAdvice 來處理異常,首先解決所有異常的處理,即便用 @ExceptionHandler() 註解的方法;而後針對特定異常作對應處理。

spring 中的事務控制註解 @Transactional

使用阿里提供的代碼檢測工具檢測到 @Transactional 時會要求明確寫明 rollbackFor ,爲何呢?來看下 rollbackFor 的做用

/**
 * Defines zero (0) or more exception {@link Class classes}, which must be
 * subclasses of {@link Throwable}, indicating which exception types must cause
 * a transaction rollback.
 * <p>By default, a transaction will be rolling back on {@link RuntimeException}
 * and {@link Error} but not on checked exceptions (business exceptions). See
 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
 * for a detailed explanation.
 * <p>This is the preferred way to construct a rollback rule (in contrast to
 * {@link #rollbackForClassName}), matching the exception class and its subclasses.
 * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
 * @see #rollbackForClassName
 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
 */
Class<? extends Throwable>[] rollbackFor() default {};

其中 "By default, a transaction will be rolling back on {@link RuntimeException} and {@link Error} but not on checked exceptions (business exceptions)" 這句比較有意思,說遇到 RuntimeException 及其子類 和 Error 會回滾,檢查異常不會回滾。那麼問題來了,根據上圖可知, SQLException 是檢查異常,難道執行 SQL 語句出錯不會回滾嗎?這顯然不符合常理,再去看 Spring 文檔

3.2.3. SQLExceptionTranslator

SQLExceptionTranslator is an interface to be implemented by classes that can translate between SQLExceptions and Spring’s own org.springframework.dao.DataAccessException, which is agnostic in regard to data access strategy. Implementations can be generic (for example, using SQLState codes for JDBC) or proprietary (for example, using Oracle error codes) for greater precision.

SQLErrorCodeSQLExceptionTranslator is the implementation of SQLExceptionTranslator that is used by default. This implementation uses specific vendor codes. It is more precise than the SQLState implementation. The error code translations are based on codes held in a JavaBean type class called SQLErrorCodes. This class is created and populated by an SQLErrorCodesFactory which as the name suggests is a factory for creating SQLErrorCodes based on the contents of a configuration file named sql-error-codes.xml. This file is populated with vendor codes and based on the DatabaseProductName taken from the DatabaseMetaData. The codes for the actual database you are using are used.

可見 spring 將 SQLException 處理成 RuntimeException 的子類 DataAccessException 異常了。

相關文章
相關標籤/搜索