foreword

Previously, when our business team handled global exceptions, we added @RestControllerAdvice+@ExceptionHandler to each business microservice to capture global exceptions. When a leader was walking through the code, he asked a question, why each microservice project has to write a set of global exception code, why not extract the global exception block into a public jar, and then each Microservices are introduced in the form of jars. Later, according to the requirements of the leader, the business team will separate out the global exception block and package it into a jar. The topic of today's discussion is about extracting the global exception and some problems that occur.

Question 1: After the global exception is extracted, how to define the business error code?

The previous team's business error code definition is: business service prefix + business module + error code. If it is an unrecognized exception, use the business prefix + fixed module code + fixed error code.
The previous global exception pseudocode is as follows

@RestControllerAdvice
@Slf4j
public class GlobalExceptionBaseHandler {

   
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public AjaxResult handleException(Exception e) {
        String servicePrifix = "U";
        String moudleCode = "001";
        String code = "0001";
        String errorCode = servicePrifix + moudleCode + code;
        String msg = e.getMessage();
        if(StringUtils.isEmpty(msg)){
            msg = "服务端异常";
        }
        log.error(msg, e);
        return AjaxResult.error(msg, errorCode);
    }
    }
Now that the global exception is extracted, how to identify the business service prefix? When it was not extracted before, the business services prefixed with each business service were directly written in the code.

At that time, our temporary solution was to solve it through spring.application.name. Because after the global exception code block is extracted, it will eventually be introduced by the service. Therefore, the pseudo code for obtaining the business service prefix can be obtained as follows

public enum  ServicePrefixEnum {

    USER_SERVICE("U","用户中心");

    private final String servicePrefix;

    private final String serviceDesc;

    ServicePrefixEnum(String servicePrefix,String serviceDesc) {
        this.servicePrefix = servicePrefix;
        this.serviceDesc = serviceDesc;
    }

    public String getServicePrefix() {
        return servicePrefix;
    }

    public String getServiceDesc() {
        return serviceDesc;
    }
}
  public String getServicePrefix(@Value("${spring.application.name}") String serviceName){
      return ServicePrefixEnum.valueOf(serviceName).getServicePrefix();
    }

However, this approach has drawbacks

Disadvantage 1: the current microservice name through hard coding of enumeration. Once the project changes the microservice name, the service prefix cannot be found.
Disadvantage 2: If the business service module is newly launched, this enumeration class has to be changed

Later, we added the configuration of custom business code to the global exception jar, and business personnel only need to configure it in the springboot configuration file, as follows

lybgeek:
  bizcode:
    prefix: U

At this time, the example of global exception transformation is as follows

@RestControllerAdvice
@Slf4j
public class GlobalExceptionBaseHandler {
    
    
    @Autowired
    private ServiceCodeProperties serviceCodeProperties;

   
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public AjaxResult handleException(Exception e) {
        String servicePrifix = serviceCodeProperties.getPrifix();
        String moudleCode = "001";
        String code = "0001";
        String errorCode = servicePrifix + moudleCode + code;
        String msg = e.getMessage();
        if(StringUtils.isEmpty(msg)){
            msg = "服务端异常";
        }
        log.error(msg, e);
        return AjaxResult.error(msg, errorCode);
    }
}

Question 2: The global exception introduces the same dependent jar as the business, but there are version differences in the jar

If the global exception is written directly as follows, there is no problem. Examples are as follows

@RestControllerAdvice
@Slf4j
public class GlobalExceptionBaseHandler {


    @Autowired
    private ServiceCodeProperties serviceCodeProperties;

    
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public AjaxResult handleException(Exception e) {
        String servicePrifix = serviceCodeProperties.getPrifix();
        String moudleCode = "001";
        String code = "0001";
        String errorCode = servicePrifix + moudleCode + code;
        String msg = e.getMessage();
        if(StringUtils.isEmpty(msg)){
            msg = "服务端异常";
        }
        log.error(msg, e);
        return AjaxResult.error(msg, HttpStatus.INTERNAL_SERVER_ERROR.value());
    }


    @ExceptionHandler(BizException.class)
    public AjaxResult handleException(BizException e)
    {
        return AjaxResult.error(e.getMessage(), e.getErrorCode());
    }

}

That is, global exceptions are directly divided into two types: business exceptions and executions. The disadvantage of this division is that there is no way to subdivide exceptions, and it also makes it impossible to subdivide the module code and business code defined by the project team. Therefore, we also list common and predictable system exceptions, examples are as follows

  /**
     *参数验证失败
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public AjaxResult handleException(ConstraintViolationException e)
    {
        log.error("参数验证失败", e);
        return AjaxResult.error("参数验证失败", HttpStatus.BAD_REQUEST.value());
    }

   /**
     * 数据库异常
     * @param e
     * @return
     */
    @ExceptionHandler({SQLException.class, MybatisPlusException.class,
            MyBatisSystemException.class, org.apache.ibatis.exceptions.PersistenceException.class,
            BadSqlGrammarException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public AjaxResult dbException(Exception e) {
        String msg = ExceptionUtil.getExceptionMessage(e);
        log.error(msg, e);
        return AjaxResult.error(msg,HttpStatus.BAD_REQUEST.value());
    }

    /**
     * 数据库中已存在该记录
     * @param e
     * @return
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public AjaxResult handleException(DuplicateKeyException e)
    {
        log.error("数据库中已存在该记录", e);
        return AjaxResult.error("数据库中已存在该记录", HttpStatus.CONFLICT.value());
    }

However, this leads to a problem, that is, the global exception and the business side use the same dependency jar, but when there is a version difference, there may be a dependency conflict, resulting in an error when the business project starts. So the solution is to add optional tags to the pom file. Examples are as follows

    <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <optional>true</optional>
        </dependency>

This tag means that the coordinates of the jar are optional, so if the project already has the coordinates of the jar imported, use the coordinates of the jar directly

Question 3: After introducing the maven optional tag, because the business does not introduce the jar required by the global exception, the project starts to report an error

The emergence of this problem: For example, our business microservice project has an aggregation layer, and some aggregation layers do not need to rely on storage media, such as mysql. Therefore, these aggregation layer project pom will not introduce dependencies related to mybatis. But our global exception needs dependencies similar to mybatis, so if we want to refer to the global exception module, we have to add additional jars that are not needed by the business side.

So springboot's conditional annotations come in handy, using the @ConditionalOnClass annotation. Examples are as follows

@RestControllerAdvice
@Slf4j
@ConditionalOnClass({SQLException.class, MybatisPlusException.class,
        MyBatisSystemException.class, org.apache.ibatis.exceptions.PersistenceException.class,
        BadSqlGrammarException.class, DuplicateKeyException.class})
public class GlobalExceptionDbHandler {




    /**
     * 数据库异常
     * @param e
     * @return
     */
    @ExceptionHandler({SQLException.class, MybatisPlusException.class,
            MyBatisSystemException.class, org.apache.ibatis.exceptions.PersistenceException.class,
            BadSqlGrammarException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public AjaxResult dbException(Exception e) {
        String msg = ExceptionUtil.getExceptionMessage(e);
        log.error(msg, e);
        return AjaxResult.error(msg,HttpStatus.BAD_REQUEST.value());
    }

    /**
     * 数据库中已存在该记录
     * @param e
     * @return
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public AjaxResult handleException(DuplicateKeyException e)
    {
        log.error("数据库中已存在该记录", e);
        return AjaxResult.error("数据库中已存在该记录", HttpStatus.CONFLICT.value());
    }
}

The function of the @ConditionalOnClass annotation is that if the specified class exists on the classpath, the class on the annotation will take effect.

At the same time, there is a detail point here, that is, global exceptions may have to be subdivided, that is, the original unified global exceptions are separated according to business scenarios, such as storage media-related storage exceptions and web-related exceptions.

Summarize

Summarize

This article mainly talks about the problems that may occur when the global exception is extracted into a jar. There are some details that are involved here, such as why the service prefix + business module code + error code should be defined, in fact, it is mainly for the purpose of troubleshooting.

Some friends may ask, you all have microservices, why don't you use distributed link tracking? According to the distributed link tracking, it is easy to locate the entire link. But when microservices are actually developed, if the company does not have an operation and maintenance platform, sometimes for cost considerations, the distributed link tracking in the testing and development environments will not be available, or even in the initial stage of online projects. Distributed link tracing. Therefore, it is very important to define the relevant business codes.

demo link

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-exception


linyb极客之路
336 声望193 粉丝