Global Handling Exception Encapsulation for springboot

Keywords: Programming Java Spring SpringBoot network

brief introduction

System exceptions, such as NullPointerException, are common in projects.If the default is not handled, springboot will respond to the default error prompt, which is not friendly to the user experience, system-level errors that the user cannot perceive, even if the error is 500, you can prompt the user with a friendly prompt similar to a server error, and so on.

In microservices, there will be exceptions in each service. Almost all services have the same default exception handling configuration, resulting in many duplicate codes. We can pull out a common starter package for these duplicate default exception handling, each service can depend on it, and custom exception handling is developed in each module.

To configure

unified-dispose-springboot-starter

This module contains functions such as exception handling and global return encapsulation, which are described below.

The full directory structure is as follows:

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── purgetiem
│   │   │           └── starter
│   │   │               └── dispose
│   │   │                   ├── GlobalDefaultConfiguration.java
│   │   │                   ├── GlobalDefaultProperties.java
│   │   │                   ├── Interceptors.java
│   │   │                   ├── Result.java
│   │   │                   ├── advice
│   │   │                   │   └── CommonResponseDataAdvice.java
│   │   │                   ├── annotation
│   │   │                   │   ├── EnableGlobalDispose.java
│   │   │                   │   └── IgnorReponseAdvice.java
│   │   │                   └── exception
│   │   │                       ├── GlobalDefaultExceptionHandler.java
│   │   │                       ├── category
│   │   │                       │   └── BusinessException.java
│   │   │                       └── error
│   │   │                           ├── CommonErrorCode.java
│   │   │                           └── details
│   │   │                               └── BusinessErrorCode.java
│   │   └── resources
│   │       ├── META-INF
│   │       │   └── spring.factories
│   │       └── dispose.properties
│   └── test
│       └── java

exception handling

Either @RestControllerAdvice or @ControllerAdvice is the exception handling comment for spring.

Let's first create the GlobalDefaultExceptionHandler global exception handling class:

@RestControllerAdvice
public class GlobalDefaultExceptionHandler {

  private static final Logger log = LoggerFactory.getLogger(GlobalDefaultExceptionHandler.class);

  /**
   * NoHandlerFoundException 404 exception handling
   */
  @ExceptionHandler(value = NoHandlerFoundException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public Result handlerNoHandlerFoundException(NoHandlerFoundException exception) {
    outPutErrorWarn(NoHandlerFoundException.class, CommonErrorCode.NOT_FOUND, exception);
    return Result.ofFail(CommonErrorCode.NOT_FOUND);
  }

  /**
   * HttpRequestMethodNotSupportedException 405 exception handling
   */
  @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  public Result handlerHttpRequestMethodNotSupportedException(
      HttpRequestMethodNotSupportedException exception) {
    outPutErrorWarn(HttpRequestMethodNotSupportedException.class,
        CommonErrorCode.METHOD_NOT_ALLOWED, exception);
    return Result.ofFail(CommonErrorCode.METHOD_NOT_ALLOWED);
  }

  /**
   * HttpMediaTypeNotSupportedException 415 exception handling
   */
  @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  public Result handlerHttpMediaTypeNotSupportedException(
      HttpMediaTypeNotSupportedException exception) {
    outPutErrorWarn(HttpMediaTypeNotSupportedException.class,
        CommonErrorCode.UNSUPPORTED_MEDIA_TYPE, exception);
    return Result.ofFail(CommonErrorCode.UNSUPPORTED_MEDIA_TYPE);
  }

  /**
   * Exception Class Capture 500 Exception Handling
   */
  @ExceptionHandler(value = Exception.class)
  public Result handlerException(Exception e) {
    return ifDepthExceptionType(e);
  }

  /**
   * Secondary Deep Check Error Type
   */
  private Result ifDepthExceptionType(Throwable throwable) {
    Throwable cause = throwable.getCause();
    if (cause instanceof ClientException) {
      return handlerClientException((ClientException) cause);
    }
    if (cause instanceof FeignException) {
      return handlerFeignException((FeignException) cause);
    }
    outPutError(Exception.class, CommonErrorCode.EXCEPTION, throwable);
    return Result.ofFail(CommonErrorCode.EXCEPTION);
  }

  /**
   * FeignException Class Capture
   */
  @ExceptionHandler(value = FeignException.class)
  public Result handlerFeignException(FeignException e) {
    outPutError(FeignException.class, CommonErrorCode.RPC_ERROR, e);
    return Result.ofFail(CommonErrorCode.RPC_ERROR);
  }

  /**
   * ClientException Class Capture
   */
  @ExceptionHandler(value = ClientException.class)
  public Result handlerClientException(ClientException e) {
    outPutError(ClientException.class, CommonErrorCode.RPC_ERROR, e);
    return Result.ofFail(CommonErrorCode.RPC_ERROR);
  }

  /**
   * BusinessException Class Capture
   */
  @ExceptionHandler(value = BusinessException.class)
  public Result handlerBusinessException(BusinessException e) {
    outPutError(BusinessException.class, CommonErrorCode.BUSINESS_ERROR, e);
    return Result.ofFail(e.getCode(), e.getMessage());
  }

  /**
   * HttpMessageNotReadableException Parameter error exception
   */
  @ExceptionHandler(HttpMessageNotReadableException.class)
  public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
    outPutError(HttpMessageNotReadableException.class, CommonErrorCode.PARAM_ERROR, e);
    String msg = String.format("%s : Error Details( %s )", CommonErrorCode.PARAM_ERROR.getMessage(),
        e.getRootCause().getMessage());
    return Result.ofFail(CommonErrorCode.PARAM_ERROR.getCode(), msg);
  }

  /**
   * BindException Parameter error exception
   */
  @ExceptionHandler(BindException.class)
  public Result handleMethodArgumentNotValidException(BindException e) {
    outPutError(BindException.class, CommonErrorCode.PARAM_ERROR, e);
    BindingResult bindingResult = e.getBindingResult();
    return getBindResultDTO(bindingResult);
  }

  private Result getBindResultDTO(BindingResult bindingResult) {
    List<FieldError> fieldErrors = bindingResult.getFieldErrors();
    if (log.isDebugEnabled()) {
      for (FieldError error : fieldErrors) {
        log.error("{} -> {}", error.getDefaultMessage(), error.getDefaultMessage());
      }
    }

    if (fieldErrors.isEmpty()) {
      log.error("validExceptionHandler error fieldErrors is empty");
      Result.ofFail(CommonErrorCode.BUSINESS_ERROR.getCode(), "");
    }

    return Result
        .ofFail(CommonErrorCode.PARAM_ERROR.getCode(), fieldErrors.get(0).getDefaultMessage());
  }

  public void outPutError(Class errorType, Enum secondaryErrorType, Throwable throwable) {
    log.error("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage(),
        throwable);
  }

  public void outPutErrorWarn(Class errorType, Enum secondaryErrorType, Throwable throwable) {
    log.warn("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage());
  }

}

The general content handles exceptions commonly found in some projects, BindException parameter exceptions, and so on.

Here, the default http status codes, such as 404, 405, 415, are also overridden.

To override this default status code, throw-exception-if-no-handler-found s and add-mappings need to be configured.

# When an error occurs, throw the exception directly (for uniform exception handling, otherwise no 404 is caught)
spring.mvc.throw-exception-if-no-handler-found=true
# Whether to turn on default resource processing, default to true
spring.resources.add-mappings=false

ps: Note that these two configurations ignore static resources.

Please take the Maternity Examination WebMvcAutoConfiguration#addResourceHandlers

To prevent unknown exceptions from not being protected, Exception defaults to returning users to the server to make small errors. Please try again later and wait for a prompt.

Specific exceptions are matched by default from small to large.

If a BindException is thrown, the custom BindException is processed in this processor.Nothing will go to its parent class to match, see the java-exception system.

Other known exceptions can be caught and handled by themselves with the @ExceptionHandler annotation.

General exception enumeration

To avoid poor maintenance of outliers, we use the CommonErrorCode enumeration to maintain common exception hints.

@Getter
public enum CommonErrorCode {

  /**
   * 404 Web The server could not find the file or script you requested.Check the URL to make sure the path is correct.
   */
  NOT_FOUND("CLOUD-404",
      String.format("Alas, this resource can't be found(%s)", HttpStatus.NOT_FOUND.getReasonPhrase())),

  /**
   * 405 The method specified in the request line is not allowed for the resource identified by the request.Make sure you set the correct MIME type for the requested resource.
   */
  METHOD_NOT_ALLOWED("CLOUD-405",
      String.format("Please try a different position(%s)", HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase())),

  /**
   * 415 Unsupported Media Type
   */
  UNSUPPORTED_MEDIA_TYPE("CLOUD-415",
      String.format("Ah, this media type is not supported(%s)", HttpStatus.UNSUPPORTED_MEDIA_TYPE.getReasonPhrase())),

  /**
   * System exception 500 server internal error
   */
  EXCEPTION("CLOUD-500", "Server is running short. Please try again later"),

  /**
   * System Current Limiting
   */
  TRAFFIC_LIMITING("CLOUD-429", "Oops, the network is crowded. Please try again later"),

  /**
   * Service Call Exception
   */
  API_GATEWAY_ERROR("API-9999", "The network is busy, please try again later"),

  /**
   * Parameter error
   */
  PARAM_ERROR("CLOUD-100", "Parameter error"),

  /**
   * Business Exceptions
   */
  BUSINESS_ERROR("CLOUD-400", "Business Exceptions"),

  /**
   * rpc Call Exception
   */
  RPC_ERROR("RPC-510", "Ah, there's a problem with the network!");

  private String code;

  private String message;

  CommonErrorCode(String code, String message) {
    this.code = code;
    this.message = message;
  }
}

In fact, it is not recommended to use Lombok annotations such as @Getter in starter packages to prevent other people from relying on the project without using lombok.

General Business Exceptions

These two types of completion are basically usable exception intercepts, but for business convenience, we create a generic business exception.

BusinessException inherits RuntimeException.

@Getter
public class BusinessException extends RuntimeException {

  private String code;
  private boolean isShowMsg = true;

  /**
   * Use enumeration parameters
   *
   * @param errorCode Exception Enumeration
   */
  public BusinessException(BusinessErrorCode errorCode) {
    super(errorCode.getMessage());
    this.code = errorCode.getCode();
  }

  /**
   * Use custom messages
   *
   * @param code value
   * @param msg details
   */
  public BusinessException(String code, String msg) {
    super(msg);
    this.code = code;
  }

}

Add BusinessException to GlobalDefaultExceptionHandler global exception interception.

/**
 * BusinessException Class Capture
 */
@ExceptionHandler(value = BusinessException.class)
public Result handlerBusinessException(BusinessException e) {
  outPutError(BusinessException.class, CommonErrorCode.BUSINESS_ERROR, e);
  return Result.ofFail(e.getCode(), e.getMessage());
}

Programs that actively throw exceptions can do so in the following ways:

throw new BusinessException(BusinessErrorCode.BUSINESS_ERROR);
// perhaps
throw new BusinessException("CLOUD800","No excess inventory");

It is not generally recommended to throw generic BusinessException exceptions directly. Instead, add exception handling classes for corresponding domains and corresponding enumeration error types to the corresponding modules.

For example, membership module: Create UserException exception classes, UserErrorCode enumerations, and UserExceptionHandler uniform intercept classes.

UserException:

@Data
public class UserException extends RuntimeException {

  private String code;
  private boolean isShowMsg = true;

  /**
   * Use enumeration parameters
   *
   * @param errorCode Exception Enumeration
   */
  public UserException(UserErrorCode errorCode) {
    super(errorCode.getMessage());
    this.setCode(errorCode.getCode());
  }

}

UserErrorCode:

@Getter
public enum UserErrorCode {
    /**
     * Permission Exception
     */
    NOT_PERMISSIONS("CLOUD401","You do not have permission to operate"),
    ;

    private String code;

    private String message;

    CommonErrorCode(String code, String message) {
        this.code = code;
        this.message = message;
    }
}

UserExceptionHandler:

@Slf4j
@RestControllerAdvice
public class UserExceptionHandler {

  /**
   * UserException Class Capture
   */
  @ExceptionHandler(value = UserException.class)
  public Result handler(UserException e) {
    log.error(e.getMessage(), e);
    return Result.ofFail(e.getCode(), e.getMessage());
  }

}

The final business use is as follows:

// Determine whether you have permission to throw exceptions
throw new UserException(UserErrorCode.NOT_PERMISSIONS);

Add spring Container

Finally, the GlobalDefaultExceptionHandler is injected into the spring container as a bean.

@Configuration
@EnableConfigurationProperties(GlobalDefaultProperties.class)
@PropertySource(value = "classpath:dispose.properties", encoding = "UTF-8")
public class GlobalDefaultConfiguration {

  @Bean
  public GlobalDefaultExceptionHandler globalDefaultExceptionHandler() {
    return new GlobalDefaultExceptionHandler();
  }

  @Bean
  public CommonResponseDataAdvice commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties){
    return new CommonResponseDataAdvice(globalDefaultProperties);
  }

}

Load the GlobalDefaultConfiguration under the resources/META-INF/spring.factories file.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.purgetime.starter.dispose.GlobalDefaultConfiguration

But this time we're using annotations to turn it on.After other projects depend on the package, you need to add @EnableGlobalDispose to turn on the feature of global interception.

Comment out the spring.factories you just created and create the EnableGlobalDispose annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(GlobalDefaultConfiguration.class)
public @interface EnableGlobalDispose {

}

Use @Import to import GlobalDefaultConfiguration.

Use

Add Dependency

<dependency>
  <groupId>io.deepblueai</groupId>
  <artifactId>unified-dispose-deepblueai-starter</artifactId>
  <version>0.1.0.RELEASE</version>
</dependency>

Startup class opens the @EnableGlobalDispose annotation.

summary

There are many duplicate code s in the project, and we can simplify them in some ways to reduce the amount of development.

Sample code address: unified-dispose-springboot

Author GitHub: Purgeyao Welcome to your attention

Posted by NewPHP_Coder on Sat, 21 Sep 2019 19:51:56 -0700