spring@Controller Advice 처리 이상 이 사용자 정의 이상 과 정확하게 일치 하지 않 습 니 다.

먼저 결론 은@Controller Advice 와@ExceptionHandler 를 사용 하여 전역 contrller 의 이상 을 처리 할 때 사용자 정의 이상 과 정확하게 일치 하려 면 contrller 의 방법 에 해당 하 는 사용자 정의 이상 을 던 지 거나 사용자 정의 이상 계승 Runtime Exception 류 가 필요 합 니 다.
질문 설명:
1.@Controller Advice 와@ExceptionHandler 를 사용 하여 전역 이상 을 처리 할 때 AppException(extends Exception)을 사용자 정의 합 니 다.일부 전역 적 인 매개 변 수 는 통일 적 으로 검증 해 야 하기 때문에 모든 contrller 방법 에 AOP 검 사 를 추가 합 니 다.매개 변수 검사 가 통과 되 지 않 으 면 AppException 을 던 집 니 다.
2.@Controller Advice 에 표 시 된 클래스 에는 주로 두 개의@ExceptionHandler 가 있 는데 각각 AppException.class 와 Throwable.class 와 일치 합 니 다.
3.테스트 할 때 전역 AOP 의 매개 변수 검사 가 통과 되 지 않 아 AppException 을 던 졌 습 니 다.그러나 이 AppException 은 우리 가 원 하 는 AppException.class 가 아 닌 Throwable.class 에 일치 하 는 것 을 발 견 했 습 니 다.
분석 과정:
한 단계
처음에 테스트 를 해 온 두 가지 서로 다른 요청(하 나 는 swagger 를 통 해 하 나 는 관광 기 를 통 해 주 소 를 입력 하고 두 가지 요청 이 비슷 해서 같은 요청 인 줄 알 았 습 니 다)으로 인해 한 방법 에 AppException 을 던 졌 습 니 다.하 나 는 없 었 습 니 다.그리고 이 문 제 를 발 견 했 을 때 나타 나 지 않 았 습 니 다.안정 적 으로 재현 할 수 없 기 때문에 저 는 AppException 에 문제 가 생 겼 을 것 이 라 고 추측 합 니 다.그래서 저 는 AppException 을 수정 하고 아버지 류 를 Runtime Exception 으로 바 꾼 후에 문제 가 해결 되 었 습 니 다.
2 단계
문제 가 해 결 된 후에 저 는 왜 이런 상황 이 발생 했 는 지 생각 했 습 니 다.자바 의 이상 시스템 에 따 르 면 Exception 을 계승 하 든 Runtime Exception 을 계승 하 든 Throwable.class 에 일치 하지 않 을 것 입 니 다.
나 는 이상 한 집행 과정 을 다시 한 번 추적 했다.대충 한 번 살 펴 보 니 아래 의 이 위치 에 차이 가 생 겼 다.

catch (InvocationTargetException ex) {
            // Unwrap for HandlerExceptionResolvers ...
            Throwable targetException = ex.getTargetException();
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException) targetException;
            }
            else if (targetException instanceof Error) {
                throw (Error) targetException;
            }
            else if (targetException instanceof Exception) {
                throw (Exception) targetException;
            }
            else {
                String text = getInvocationErrorMessage("Failed to invoke handler method", args);
                throw new IllegalStateException(text, targetException);
            }
        }
성공 은 Exception,실 패 는 Runtime Exception.
이 럴 때@Controller Advice 에 표 시 된 클래스 가 되면 문제 가 발생 합 니 다.AppException 을 계승 하 는 것 은 Runtime Exception 과 동급 이기 때문에 runtime Exception 이라는 판단 조건 에서 던 진 이상 은 AppException 에 일치 하지 않 을 수 밖 에 없습니다.
이 때 이상 유형 을 자세히 비교 해 보면 정확 한 이상 유형 을 발견 할 수 있 을 때 AppException,잘못된 이상 유형 을 발견 할 때 java.lang.reflect.Undeclared ThrowableException,내부 에 AppException 이 포함 되 어 있 습 니 다.
JDK 의 자바 doc 는 Undeclared Throwable Exception 을 설명 합 니 다.프 록 시 인 스 턴 스 호출 처리 프로그램의 invoke 방법 이 검 사 를 거 친 이상(Runtime Exception 이나 Error 에 할당 할 수 없 는 Throwable)을 던 지고 이 이상 을 이 방법 에 할당 할 수 없 는 throws 서브 국 에서 설명 한 이상 류 를 던 지면 프 록 시 인 스 턴 스 의 방법 으로 이 이상 을 던 집 니 다.
AppException 이 Exception 에 계승 되 기 때문에 대리 가 던 진 이상 은 AppException 을 포함 한 Undeclared ThrowableException 입 니 다.@Controller Advice 가 일치 할 때 자 연 스 럽 게 일치 하지 않 습 니 다.
앱 익 스 펙 션 이 런 타임 익 스 펙 션 에 계승 되 었 을 때 던 진 이상 은 여전히 앱 익 스 펙 션 이 므 로 일치 할 수 있 습 니 다.
결론:그래서 해결 방법 은 두 가지 가 있 습 니 다.AppException 은 Runtime Exception 이나 Controller 를 계승 하 는 방법 으로 AppException 이상 을 던 집 니 다.
Spring 의@ExceptionHandler 와@Controller Advice 는 이상 처 리 를 통일 합 니 다.
이전에 코드 를 두 드 렸 을 때 각종 try..catch 를 피 할 수 없 었 습 니 다.업무 가 복잡 하면 모두 try..catch 라 는 것 을 알 게 될 것 입 니 다.

try{
    ..........
}catch(Exception1 e){
    ..........
}catch(Exception2 e){
    ...........
}catch(Exception3 e){
    ...........
}
이렇게 하면 사실 코드 가 간결 하지 도 않 고 예 쁘 기도 하고 두 드 리 기도 귀 찮 습 니 다.보통 우 리 는 차단기 로 처리 할 생각 을 할 수 있 습 니 다.그러나 지금 Spring 이 이렇게 핫 한 이상 AOP 여러분 도 낯 설 지 않 습 니 다.그러면 Spring 은 반드시 우 리 를 위해 이 해결 방법 을 생각해 주 었 을 것 입 니 다.과연:
@ExceptionHandler
소스 코드

//          
@Target({ElementType.METHOD})
//      
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
 //value()       
    Class<? extends Throwable>[] value() default {};
}
@ControllerAdvice
소스 코드

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//bean    spring    
@Component
public @interface ControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};
    @AliasFor("value")
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<?>[] assignableTypes() default {};
    Class<? extends Annotation>[] annotations() default {};
}
이름 에서 알 수 있 듯 이 대체적으로 컨트롤 러 가 강화 되 었 다 는 뜻 이다.
그래서 위 와 결합 하면 알 수 있 듯 이@ExceptionHandler 를 사용 하면 이상 을 처리 할 수 있 지만 현재 Controller 에서 만 이상 을 처리 할 수 있 습 니 다.
@Controller Advice 는 basePackage 의 모든 controller 를 설정 할 수 있 기 때문에 두 가 지 를 결합 하여 사용 하면 전역 의 이상 을 처리 할 수 있 습 니 다.
코드
여기 서 설명 해 야 할 것 은 이 통 일 된 이상 처리 류 도 Controller Advice,즉 제어 층 절단면 을 바탕 으로 하 는 것 입 니 다.필터 가 던 진 이상 이 라면 잡 히 지 않 습 니 다!!
@Controller Advice 주해 의 클래스 에서 그 방법 은@ExceptionHandler 주해 로 수식 하 는 방법 으로 해당 하 는 이상 을 해당 하 는 방법 으로 처리 합 니 다.

@ExceptionHandler({IOException.class})
public Result handleException(IOExceptione) {
    log.error("[handleException] ", e);
    return ResultUtil.failureDefaultError();
  }
예 를 들 어 이것 은 IO 이상 을 포착 하고 처리 하 는 것 이다.
잔말 말고 코드:

package com.zgd.shop.core.exception;
import com.zgd.shop.core.error.ErrorCache;
import com.zgd.shop.core.result.Result;
import com.zgd.shop.core.result.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Set;
/**
 * GlobalExceptionHandle
 *        
 *
 * @author zgd
 * @date 2019/7/19 11:01
 */
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandle {
  /**
   *       
   */
  private final static String BASE_PARAM_ERR_CODE = "BASE-PARAM-01";
  private final static String BASE_PARAM_ERR_MSG = "       ";
  /**
   *      
   */
  private final static String BASE_BAD_REQUEST_ERR_CODE = "BASE-PARAM-02";
  private final static String BASE_BAD_REQUEST_ERR_MSG = "     ";
  /**
   *        
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.OK)
  @ExceptionHandler({Exception.class})
  public Result handleException(Exception e) {
    log.error("[handleException] ", e);
    return ResultUtil.failureDefaultError();
  }
  /**
   *         
   *
   * @param ex
   * @return
   */
  @ResponseStatus(HttpStatus.OK)
  @ExceptionHandler({BizServiceException.class})
  public Result serviceExceptionHandler(BizServiceException ex) {
    String errorCode = ex.getErrCode();
    String msg = ex.getErrMsg() == null ? "" : ex.getErrMsg();
    String innerErrMsg;
    String outerErrMsg;
    if (BASE_PARAM_ERR_CODE.equalsIgnoreCase(errorCode)) {
      innerErrMsg = "       :" + msg;
      outerErrMsg = BASE_PARAM_ERR_MSG;
    } else if (ex.isInnerError()) {
      innerErrMsg = ErrorCache.getInternalMsg(errorCode);
      outerErrMsg = ErrorCache.getMsg(errorCode);
      if (StringUtils.isNotBlank(msg)) {
        innerErrMsg = innerErrMsg + "," + msg;
        outerErrMsg = outerErrMsg + "," + msg;
      }
    } else {
      innerErrMsg = msg;
      outerErrMsg = msg;
    }
    log.info("【   】:{},【       】:{},【       】:{}", errorCode, innerErrMsg, outerErrMsg);
    return ResultUtil.failure(errorCode, outerErrMsg);
  }
  /**
   *   servlet         
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({MissingServletRequestParameterException.class})
  public Result handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
    log.warn("[handleMissingServletRequestParameterException]     : " + e.getParameterName());
    return ResultUtil.failure(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);
  }
  /**
   *              ,     ,               
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.OK)
  @ExceptionHandler({HttpMessageNotReadableException.class})
  public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
    log.warn("[handleHttpMessageNotReadableException]       :", e);
    return ResultUtil.failure(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);
  }
  /**
   *            
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({MethodArgumentNotValidException.class})
  public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    BindingResult result = e.getBindingResult();
    String message = getBindResultMessage(result);
    log.warn("[handleMethodArgumentNotValidException]       :" + message);
    return ResultUtil.failure(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);
  }
  private String getBindResultMessage(BindingResult result) {
    FieldError error = result.getFieldError();
    String field = error != null ? error.getField() : " ";
    String code = error != null ? error.getDefaultMessage() : " ";
    return String.format("%s:%s", field, code);
  }
  /**
   *              
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({MethodArgumentTypeMismatchException.class})
  public Result handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
    log.warn("[handleMethodArgumentTypeMismatchException]            : ", e);
    return ResultUtil.failure(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);
  }
  /**
   *        controller        
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({BindException.class})
  public Result handleHttpMessageNotReadableException(BindException e) {
    BindingResult result = e.getBindingResult();
    String message = getBindResultMessage(result);
    log.warn("[handleHttpMessageNotReadableException]       :" + message);
    return ResultUtil.failure(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);
  }
  /**
   * javax.validation:validation-api          
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({ConstraintViolationException.class})
  public Result handleServiceException(ConstraintViolationException e) {
    Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
    ConstraintViolation<?> violation = violations.iterator().next();
    String message = violation.getMessage();
    log.warn("[handleServiceException]       :" + message);
    return ResultUtil.failure(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);
  }
  /**
   * javax.validation            
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({ValidationException.class})
  public Result handleValidationException(ValidationException e) {
    log.warn("[handleValidationException]       :", e);
    return ResultUtil.failure(BASE_PARAM_ERR_CODE, BASE_PARAM_ERR_MSG);
  }
  /**
   *               
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
  @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
  public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
    log.warn("[handleHttpRequestMethodNotSupportedException]          : ", e);
    return ResultUtil.failure(BASE_BAD_REQUEST_ERR_CODE, BASE_BAD_REQUEST_ERR_MSG);
  }
  /**
   *               
   *
   * @param e
   * @return
   */
  @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
  @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
  public Result handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
    log.warn("[handleHttpMediaTypeNotSupportedException]          : ", e);
    return ResultUtil.failure(BASE_BAD_REQUEST_ERR_CODE, BASE_BAD_REQUEST_ERR_MSG);
  }
}
반환 값 에 대해 서 는 controller 계층 방법의 반환 값 으로 이해 할 수 있 으 며,@Response Body 나 페이지 로 돌아 갈 수 있 습 니 다.여 기 는@Response Body 의 Result<>입 니 다.앞 뒤 가 분 리 됩 니 다.
우리 도 스스로 수요 에 따라 더 많은 이상 유형 을 포착 할 수 있다.
사용자 정의 이상 유형 을 포함 합 니 다.예 를 들 면:

package com.zgd.shop.core.exception;
import lombok.Data;
/**
 * BizServiceException
 *        
 * @author zgd
 * @date 2019/7/19 11:04
 */
@Data
public class BizServiceException extends RuntimeException{
  private String errCode;
  private String errMsg;
  private boolean isInnerError;
  public BizServiceException(){
    this.isInnerError=false;
  }
  public BizServiceException(String errCode){
    this.errCode =errCode;
    this.isInnerError = false;
  }
  public BizServiceException(String errCode,boolean isInnerError){
    this.errCode =errCode;
    this.isInnerError = isInnerError;
  }
  public BizServiceException(String errCode,String errMsg){
    this.errCode =errCode;
    this.errMsg = errMsg;
    this.isInnerError = false;
  }
  public BizServiceException(String errCode,String errMsg,boolean isInnerError){
    this.errCode =errCode;
    this.errMsg = errMsg;
    this.isInnerError = isInnerError;
  }
}
이상 은 개인 적 인 경험 이 므 로 여러분 에 게 참고 가 되 기 를 바 랍 니 다.여러분 들 도 저 희 를 많이 응원 해 주시 기 바 랍 니 다.

좋은 웹페이지 즐겨찾기