你真的瞭解spring boot全局異常處理嗎

前言

舒適提示:本文使用的spring boot版本爲2.1.8.RELEASE。html

全局異常處理你們應該都接觸過,也不是什麼難事,網上一搜一大堆,可是寫的對不對只能本身測試了,運氣好的話找了一個能用的,運氣很差的可能會煩到你懷疑人生。java

我就是那個運氣很差的人,也是由於碰到了一些問題,因此纔會有這篇文章吧。web

優點

全局異常處理主要的好處:spring

  1. 統一接口返回格式。好比說請求方法錯誤,原本是get可是用成了post,這種錯誤都不會通過本身寫的代碼,沒法控制返回的數據格式。
  2. 方便記錄日誌。跟上面同樣的道理,有的錯誤框架直接返回了,可能都沒有日誌記錄。
  3. 減小冗餘代碼。統一處理總比每個接口本身處理要好吧。

作法

通常的方案都是基於@ControllerAdvice@ExceptionHandler作的。apache

  • @ControllerAdvice至關於controller的切面,主要用於@ExceptionHandler, @InitBinder@ModelAttribute,使註解標註的方法對每個controller都起做用。默認對全部controller都起做用,固然也能夠經過@ControllerAdvice註解中的一些屬性選定符合條件的controller。
  • @ExceptionHandler用於異常處理的註解,能夠經過value指定處理哪一種類型的異常還能夠與@ResponseStatus搭配使用,處理特定的http錯誤。標記的方法入參與返回值都有很大的靈活性,具體能夠看註釋也能夠後邊的深度探究。

其實有上面兩個已經夠了,還能夠繼承ResponseEntityExceptionHandlerjson

讀一下這個類的註釋就知道它是幹啥的了微信

ResponseEntityExceptionHandler

大概意思就是這個類是爲了方便統一異常處理的基類,可是要注意返回的是ResponseEntity,若是不須要往響應體中寫內容或者返回一個視圖,可使用DefaultHandlerExceptionResolversession

能夠看一下這個類的實現mvc

/**
    * Provides handling for standard Spring MVC exceptions.
    * @param ex the target exception
    * @param request the current request
    */
@ExceptionHandler({
        HttpRequestMethodNotSupportedException.class,
        HttpMediaTypeNotSupportedException.class,
        HttpMediaTypeNotAcceptableException.class,
        MissingPathVariableException.class,
        MissingServletRequestParameterException.class,
        ServletRequestBindingException.class,
        ConversionNotSupportedException.class,
        TypeMismatchException.class,
        HttpMessageNotReadableException.class,
        HttpMessageNotWritableException.class,
        MethodArgumentNotValidException.class,
        MissingServletRequestPartException.class,
        BindException.class,
        NoHandlerFoundException.class,
        AsyncRequestTimeoutException.class
    })
@Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
    HttpHeaders headers = new HttpHeaders();

    if (ex instanceof HttpRequestMethodNotSupportedException) {
        HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
        return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMediaTypeNotSupportedException) {
        HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
        return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMediaTypeNotAcceptableException) {
        HttpStatus status = HttpStatus.NOT_ACCEPTABLE;
        return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request);
    }
    else if (ex instanceof MissingPathVariableException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request);
    }
    else if (ex instanceof MissingServletRequestParameterException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request);
    }
    else if (ex instanceof ServletRequestBindingException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request);
    }
    else if (ex instanceof ConversionNotSupportedException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof TypeMismatchException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleTypeMismatch((TypeMismatchException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMessageNotReadableException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMessageNotWritableException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request);
    }
    else if (ex instanceof MethodArgumentNotValidException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
    }
    else if (ex instanceof MissingServletRequestPartException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request);
    }
    else if (ex instanceof BindException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleBindException((BindException) ex, headers, status, request);
    }
    else if (ex instanceof NoHandlerFoundException) {
        HttpStatus status = HttpStatus.NOT_FOUND;
        return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request);
    }
    else if (ex instanceof AsyncRequestTimeoutException) {
        HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE;
        return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request);
    }
    else {
        // Unknown exception, typically a wrapper with a common MVC exception as cause
        // (since @ExceptionHandler type declarations also match first-level causes):
        // We only deal with top-level MVC exceptions here, so let's rethrow the given
        // exception for further processing through the HandlerExceptionResolver chain.
        throw ex;
    }
}

對於每一種異常,能夠重寫相應的方法。同時每一個異常的具體處理方法最後又調用了同一個方法app

/**
     * A single place to customize the response body of all exception types.
     * <p>The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE}
     * request attribute and creates a {@link ResponseEntity} from the given
     * body, headers, and status.
     * @param ex the exception
     * @param body the body for the response
     * @param headers the headers for the response
     * @param status the response status
     * @param request the current request
     */
    protected ResponseEntity<Object> handleExceptionInternal(
            Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {

        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        return new ResponseEntity<>(body, headers, status);
    }

通用的處理能夠在這個方法中實現,返回結果中能夠自定義body、header、以及http status。好比對於一些異常,可能並不但願http status code爲500,而是返回200,用body裏的code再去判斷是成功仍是失敗,用這種方法就很是容易實現。

使用這種方案,咱們本身寫的類就應該相似這種

package com.zworks.aircraft.web.advice;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class AirCraftExceptionHandler extends ResponseEntityExceptionHandler {

}

實戰

能夠去Spring Initializr去初始化工程

依賴只須要web就能夠了,爲了方即可以添加lombok

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zworks</groupId>
    <artifactId>aircraft</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>aircraft</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

定義一個通用返回結果

package com.zworks.aircraft.model;

import lombok.Data;

@Data
public class RespMsg<T> {

    private int code = 200;

    private String msg;

    private T data;

    public RespMsg() {
    }

    public RespMsg(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public RespMsg(T data) {
        this.data = data;
    }

    public static <T> RespMsg<T> success() {
        return new RespMsg();
    }

    public static <T> RespMsg<T> success(T data) {
        return new RespMsg(data);
    }

    public static RespMsg failed(int code, String msg) {
        return new RespMsg(code, msg);
    }
}

定義用戶實體類,在字段上加上校驗,方便演示錯誤的請求

package com.zworks.aircraft.model;

import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class User {

    @NotNull(message = "id不能爲空")
    private Long id;

    @NotBlank(message = "名稱不能爲空")
    private String name;
}

定義一個Controller,什麼都不用幹,只須要驗證參數的合法性,而後返回正確便可。

package com.zworks.aircraft.web.controller;

import com.zworks.aircraft.model.RespMsg;
import com.zworks.aircraft.model.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
public class UserController {

    @PostMapping(value = "user")
    public RespMsg user(@Valid @RequestBody User user) {
        return RespMsg.success();
    }
}

若是參數正確,返回以下

{
    "code": 200,
    "msg": null,
    "data": null
}

但若是參數錯誤,好比不傳入name,則會返回

{
    "timestamp": "2019-09-07T14:04:54.440+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotBlank.user.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "名稱不能爲空",
            "objectName": "user",
            "field": "name",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotBlank"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 1",
    "path": "/user"
}

這確定不是咱們想要的結果。

按照剛纔的方案,實現一個全局異常處理。

package com.zworks.aircraft.web.advice;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class AirCraftExceptionHandler extends ResponseEntityExceptionHandler {

}

這個時候其實已經生效了,只是默認的什麼數據都沒有返回,能夠重寫handleExceptionInternal方法

package com.zworks.aircraft.web.advice;

import com.zworks.aircraft.model.RespMsg;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.util.WebUtils;

@ControllerAdvice
public class AirCraftExceptionHandler extends ResponseEntityExceptionHandler {
    protected ResponseEntity<Object> handleExceptionInternal(
            Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        logger.error(ex.getMessage(), ex);//打印異常信息
        RespMsg respMsg = RespMsg.failed(status.value(), ex.getMessage());//使用http狀態碼做爲返回體的code,同時把異常信息返回
        return new ResponseEntity<>(respMsg, headers, status);
    }
}

這時返回的信息以下

{
    "code": 400,
    "msg": "Validation failed for argument [0] in public com.zworks.aircraft.model.RespMsg com.zworks.aircraft.web.controller.UserController.user(com.zworks.aircraft.model.User): [Field error in object 'user' on field 'name': rejected value [null]; codes [NotBlank.user.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [不能爲空]] ",
    "data": null
}

雖然已是咱們想要的格式了,可是返回的內容不太已讀。

剛纔打印了日誌,能夠看到報錯爲MethodArgumentNotValidException,能夠經過重寫handleMethodArgumentNotValid方法改變異常信息

package com.zworks.aircraft.web.advice;

import com.zworks.aircraft.model.RespMsg;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.util.WebUtils;

@ControllerAdvice
public class AirCraftExceptionHandler extends ResponseEntityExceptionHandler {
    protected ResponseEntity<Object> handleExceptionInternal(
            Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        logger.error(ex.getMessage(), ex);//打印異常信息
        RespMsg respMsg = RespMsg.failed(status.value(), ex.getMessage());//使用http狀態碼做爲返回體的code,同時把異常信息返回
        return new ResponseEntity<>(respMsg, headers, status);
    }

    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        StringBuffer sb = new StringBuffer();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            sb.append(error.getDefaultMessage()).append(";");
        });
        RespMsg respMsg = RespMsg.failed(status.value(), sb.toString());
        return new ResponseEntity<>(respMsg, headers, HttpStatus.OK);//這裏能夠根據具體狀況改變狀態碼
    }
}

返回以下

{
    "code": 400,
    "msg": "名稱不能爲空;",
    "data": null
}

同時須要注意的是,因爲我修改了返回碼,此次請求返回的是http status code是200而不是以前的400。

拓展

默認異常處理

在最開始沒有自定義全局異常處理的時候,也返回了錯誤信息,那這個是誰處理的呢。

默認spring boot 會提供一個/error映射處理全部的異常。並且會根據請求的不一樣返回一個頁面或是json。

ErrorMvcAutoConfiguration類中能夠看到

@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}

@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}

均可以經過本身定義相應的類進行定製化。

以前因爲被網上文章誤導,使用前面那種方式沒成功,爲了記錄異常,我還寫了個切面。

package com.zworks.aircraft.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
@Slf4j
public class ErrorControllerAspect {

    @Before("execution(public * org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.*(..))")
    public void before(JoinPoint joinPoint) {
        Object arg = joinPoint.getArgs()[0];
        if (arg instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) arg;
            log.error((String) request.getAttribute("javax.servlet.error.message"), (Throwable) request.getAttribute("javax.servlet.error.exception"));
        }
    }
}

返回類型

我被坑主要是由於看了一篇文章,Validation in Spring Boot,我不能說必定是文章的問題,至少我按他的沒作對。

異常處理是這麼寫的

package com.zworks.aircraft.web.advice;

import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class AirCraftExceptionHandler {
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return errors;
    }
}

注意這裏返回的是Map類型

返回和沒加是同樣的

{
    "timestamp": "2019-09-07T14:30:21.688+0000",
    "status": 500,
    "error": "Internal Server Error",
    "errors": [
        {
            "codes": [
                "NotBlank.user.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "名稱不能爲空",
            "objectName": "user",
            "field": "name",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotBlank"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 1",
    "path": "/user"
}

而後我在那個方法上打了斷點,發現代碼竟然執行了

代碼停在了斷點處

而後我就跟代碼,也對比了兩種實現的執行狀況,發如今HandlerMethodReturnValueHandlerCompositeselectHandler方法中會根據返回的類型決定使用那個handler進行處理

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

如今的handler是MapMethodProcessor

而若是咱們將返回值格式改一下

package com.zworks.aircraft.web.advice;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class AirCraftExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return new ResponseEntity<>(errors, new HttpHeaders(), HttpStatus.OK);//這裏能夠根據具體狀況改變狀態碼
    }
}

會發現handler變成了HttpEntityMethodProcessor

返回值也沒問題了

{
    "name": "名稱不能爲空"
}

請求參數

大部分人從網上查到了信息,都不會考慮請求參數該傳那些,順序有沒有影響吧,受前面返回類型的影響,我也看了下請求參數相關的。

能夠在本身的方法中打個斷點,而後不斷找上層調用。

InvocableHandlerMethodinvokeForRequest方法中能夠看到

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    return doInvoke(args);
}

會獲取參數,而後調用咱們的方法

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        }

        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                // Leave stack trace for later, exception may actually be resolved and handled...
                if (logger.isDebugEnabled()) {
                    String exMsg = ex.getMessage();
                    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                        logger.debug(formatArgumentError(parameter, exMsg));
                    }
                }
                throw ex;
            }
        }
        return args;
    }

findProvidedArgument中會根據類型找參數

@Nullable
    protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
        if (!ObjectUtils.isEmpty(providedArgs)) {
            for (Object providedArg : providedArgs) {
                if (parameter.getParameterType().isInstance(providedArg)) {
                    return providedArg;
                }
            }
        }
        return null;
    }

providedArgs中有兩個類型,一個是org.springframework.web.bind.MethodArgumentNotValidException類型,也就是異常,另外一個是org.springframework.web.method.HandlerMethod。因此若是單寫一個異常是沒問題的。

若是經過providedArgs沒取到,會從resolvers裏去取。

只要這些resolvers能支持的參數,均可以取到。
resolvers
好比看到了上面有ServletRequestMethodArgumentResolver類,能夠看到支持HttpSession

@Override
public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
            ServletRequest.class.isAssignableFrom(paramType) ||
            MultipartRequest.class.isAssignableFrom(paramType) ||
            HttpSession.class.isAssignableFrom(paramType) ||
            (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
            Principal.class.isAssignableFrom(paramType) ||
            InputStream.class.isAssignableFrom(paramType) ||
            Reader.class.isAssignableFrom(paramType) ||
            HttpMethod.class == paramType ||
            Locale.class == paramType ||
            TimeZone.class == paramType ||
            ZoneId.class == paramType);
}

咱們測試下,在參數裏添加一個HttpSession

package com.zworks.aircraft.web.advice;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;

import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@ControllerAdvice
public class AirCraftExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity handleValidationExceptions(WebRequest request, MethodArgumentNotValidException ex, HttpSession httpSession) {
        log.info("sessionId:{}", httpSession.getId());
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return new ResponseEntity<>(errors, new HttpHeaders(), HttpStatus.OK);//這裏能夠根據具體狀況改變狀態碼
    }
}

能夠看到已經輸出了sessionId

2019-09-07 23:15:43.257  INFO 25692 --- [nio-8080-exec-1] c.z.a.w.advice.AirCraftExceptionHandler  : sessionId:B9C6E5BA4156BBBC5931FFE9B259E5ED

請求參數的處理方式和返回類型的處理方式是否很類似呢

後記

全局異常處理我也處理過不少次了,大部分也都是網上找一篇,雖然每次不會花不少時間,可是作了這麼多遍也只是有個大概印象,並無很深刻的去探究過。整理一下,知識才會變成本身的。

看到了這裏必定是真愛了,關注微信公衆號【憨憨的春天】第一時間獲取更新
圖片描述

參考

相關文章
相關標籤/搜索