How to Elegantly Design Java Exceptions

Introduction

Exception handling is one of the essential operations in program development, but how to handle exceptions correctly and gracefully is indeed a knowledge. The author will talk about how I handle exceptions based on my own development experience.
Since this article only talks about experience and does not involve basic knowledge, if the reader is still vague about the concept of exception, please check the basic knowledge first.

How to choose an exception type

type of exception

As we know, the superclass of exceptions in java is java.lang.Throwable (hereinafter omitted as Throwable), which has two more important subclasses, java.lang.Exception (hereinafter omitted as Exception) and java.lang.Error (hereinafter omitted as Error), where Error is managed by the JVM virtual machine, such as the well-known OutOfMemoryError exception, etc., so we do not focus on the Error exception in this article, then let's talk about the Exception exception in detail.
Exception has an important subclass called RuntimeException. We call RuntimeException or other subclasses inherited from RuntimeException as unchecked exceptions (unchecked Exception), and other subclasses inherited from Exception exceptions are called checked exceptions (checked Exception). This article focuses on two types of exceptions, checked exceptions and unchecked exceptions.

How to choose exceptions

From the author's development experience, if in an application, you need to develop a method (such as a service method of a function), if there may be an exception in the middle of this method, then you need to consider whether the caller can handle the exception after it occurs, And do you want the caller to handle it, if the caller can handle it, and you want the caller to handle it, throw a checked exception, remind the caller when using your method to take into account if an exception is thrown If you handle it, similarly, if you think it's an accidental exception when you write a method, theoretically, you think you may encounter some problems at runtime, and these problems may not necessarily happen, and you don't need to call If the operator shows that the business process operation is judged by exception, then you can use an unchecked exception such as RuntimeException
.
So, please follow my train of thought and slowly understand it.

When do you need to throw an exception

First we need to understand a problem, when do we need to throw an exception? The design of exceptions is convenient for developers to use, but they are not used indiscriminately. The author has also asked many friends about when to throw exceptions, and there are not many who can give accurate answers. In fact, this problem is very simple, if you feel that some "problems" cannot be solved, then you can throw an exception. For example, if you are writing a service, when you write a certain piece of code, you find that there may be problems, then please throw an exception. Believe me, it will be the best time for you to throw an exception at this time.

what exception should be thrown

After understanding when an exception needs to be thrown, let's think about another question. When we throw an exception, what kind of exception should we choose? Is it a checked exception or an unchecked exception (RuntimeException)? Let me give an example of this problem. Let’s start with the checked exception. For example, if there is such a business logic, it is necessary to read a certain data from a certain file. This reading operation may be caused by other problems such as the deletion of the file. If there is a read error, then it is necessary to obtain this data from the redis or mysql database. Refer to the following code, getKey(Integer) is the entry program.

public String getKey(Integer key){
    String  value;
    try {
        InputStream inputStream = getFiles("/file/nofile");
        //接下来从流中读取key的value指
        value = ...;
    } catch (Exception e) {
        //如果抛出异常将从mysql或者redis进行取之
        value = ...;
    }
}

public InputStream getFiles(String path) throws Exception {
    File file = new File(path);
    InputStream inputStream = null;
    try {
        inputStream = new BufferedInputStream(new FileInputStream(file));
    } catch (FileNotFoundException e) {
        throw new Exception("I/O读取错误",e.getCause());
    }
    return inputStream;
}

ok, after reading the above code, you may have some ideas in your mind. It turns out that checked exceptions can control the logic of obligations. Yes, yes, business logic can really be controlled through checked exceptions, but remember not to use them in this way, we should be reasonable Throwing an exception, because the program itself is the process, the function of the exception is just an excuse to find when you can't go on. It cannot be used as the entry or exit of the control program flow. If it is used in this way, it is the exception This will lead to an increase in the complexity of the code, an increase in coupling, and a decrease in code readability. So is it necessary not to use such an exception? In fact, it is not. When there is such a demand, we can use it like this, but remember not to use it as a tool or means to control the process. So when exactly should such an exception be thrown? It should be considered that if the caller makes an error, the caller must handle the error. Only when such requirements are met, we will consider using checked exceptions.
Next, let's take a look at the unchecked exception (RuntimeException). We actually see a lot of exceptions such as RuntimeException, such as java.lang.NullPointerException/java.lang.IllegalArgumentException, etc. So when should we throw this exception? ? When we are writing a method, we may encounter an error by accident. We think that this problem may occur at runtime, and theoretically, if there is no such problem, when the program will execute normally, it It is not mandatory for the caller to catch this exception. At this time, a RuntimeException exception is thrown. For example, when a path is passed, a File object corresponding to the path needs to be returned:

public void test() {
    myTest.getFiles("");
}

public File getFiles(String path) {
    if(null == path || "".equals(path)){
        throw  new NullPointerException("路径不能为空!");
    }
    File file = new File(path);

    return file;
}

The above example shows that if the caller calls getFiles(String) if the path is empty, then a null pointer exception (which is a subclass of RuntimeException) is thrown, and the caller does not need to explicitly perform a try...catch... operation to force processing. This requires the caller to verify before calling such a method to avoid RuntimeException. As follows:

public void test() {
    String path = "/a/b.png";
    if(null != path && !"".equals(path)){
        myTest.getFiles("");
    }
}

public File getFiles(String path) {
    if(null == path || "".equals(path)){
        throw  new NullPointerException("路径不能为空!");
    }
    File file = new File(path);

    return file;
}

Which exception should be used

Through the above description and examples, we can conclude that the difference between RuntimeException and checked exception is: whether it is mandatory for the caller to handle this exception, and if it is mandatory for the caller to deal with it, then the checked exception is used , otherwise an unchecked exception (RuntimeException) is selected. In general, if there are no special requirements, we recommend using the RuntimeException exception.

Scenario introduction and technology selection

Architecture description

As we know, traditional projects are developed based on the MVC framework. This article mainly uses the design of restful style interface to experience the elegance of exception handling.
We focus on the restful api layer (similar to the controller layer in the web) and the service layer, study how exceptions are thrown in the service, and how the api layer captures and converts exceptions.
The technologies used are: spring-boot, jpa (hibernate), mysql. If you are not familiar with these technologies, readers need to read the relevant materials by themselves.

Business Scenario Description

Choose a relatively simple business scenario, taking the delivery address management in e-commerce as an example. When users purchase goods on the mobile terminal, they need to manage the delivery address. In the project, provide some api interfaces for the mobile terminal to access. , such as: add shipping address, delete shipping address, change shipping address, default shipping address setting, shipping address list query, single shipping address query and other interfaces.

Build Constraints

ok, this is a very basic business scenario set up, of course, no matter what kind of api operation, it contains some rules:

  • Add delivery address:
    input parameters:

    1. userid
    2. Shipping address entity information

      constraint:

    3. The user id cannot be empty, and the user does exist

    4. Required fields for shipping address cannot be empty
    5. If the user does not have a shipping address, set it as the default shipping address when the shipping address is created—
  • Delete delivery address:
    input parameters:

    1. userid
    2. delivery address id

    constraint:

    1. The user id cannot be empty, and the user does exist
    2. The delivery address cannot be empty, and the delivery address does exist

      1. Determine whether this delivery address is the user's delivery address
      2. Determine whether this shipping address is the default shipping address, if it is the default shipping address, it cannot be deleted
  • Change the delivery address:
    Input:

    1. userid

      1. delivery address id

      constraint:

      1. The user id cannot be empty, and the user does exist
    2. The delivery address cannot be empty, and the delivery address does exist
    3. Determine whether this delivery address is the user's delivery address

  • Default address setting:
    Input parameters:

    1. userid
    2. delivery address id

    constraint:

    1. The user id cannot be empty, and the user does exist
      1. The delivery address cannot be empty, and the delivery address does exist
      2. Determine whether this delivery address is the user's delivery address
  • Shipping address list query:
    input parameters:

    1. userid

      constraint:

    2. The user id cannot be empty, and the user does exist

  • Single delivery address query:
    input parameters:

    1. userid

      1. delivery address id

      constraint:

      1. The user id cannot be empty, and the user does exist
    2. The delivery address cannot be empty, and the delivery address does exist
    3. Determine whether this delivery address is the user's delivery address

Constraint judgment and technology selection

For the list of constraints and functions listed above, I select several typical exception handling scenarios for analysis: add a shipping address, delete a shipping address, and get a list of shipping addresses.
So what are the necessary knowledge reserves? Let's take a look at the function of the delivery address:
the user id and the entity information of the delivery address need to be verified when adding the delivery address. choice? The traditional judgment is as follows:

/**
 * 添加地址
 * @param uid
 * @param address
 * @return
 */
public Address addAddress(Integer uid,Address address){
    if(null != uid){
        //进行处理..
    }
    return null;
}

In the above example, if only uid is determined to be empty, it is fine. If some necessary attributes in the entity address are determined to be empty, in the case of many fields, this is nothing but catastrophic.
So how should we make these judgments about the participation? Let me introduce two knowledge points to you:

  1. The Preconditions class in Guava implements the judgment of many input methods
  2. The validation specification of jsr 303 (the current implementation is hibernate-validator implemented by hibernate)
    If these two recommended technologies are used, the judgment of input parameters will become much simpler. It is recommended that you use these mature technologies and jar toolkits more, which can reduce a lot of unnecessary workload. We just need to focus on the business logic. It will not delay more time because of these judgments.

How to elegantly design java exceptions

domain introduction

According to the project scenario, two domain models are required, one is the user entity and the other is the address entity. The
Address domain is as follows:

@Entity
@Data
public class Address {
    @Id
    @GeneratedValue
    private Integer id;
    private String province;//省
    private String city;//市
    private String county;//区
    private Boolean isDefault;//是否是默认地址

    @ManyToOne(cascade={CascadeType.ALL})
    @JoinColumn(name="uid")
    private User user;
}

User domain is as follows:

@Entity
@Data
public class User {
    @Id
   @GeneratedValue
   private Integer id;
   private String name;//姓名

    @OneToMany(cascade= CascadeType.ALL,mappedBy="user",fetch = FetchType.LAZY)
        private Set<Address> addresses;
}

ok, the above is a model relationship, and the relationship between user and shipping address is a 1-n relationship. The @Data above uses a tool called lombok, which automatically generates methods such as Setter and Getter, which is very convenient to use. Interested readers can learn about it by themselves.

Introduction

For the data connection layer, we use the spring-data-jpa framework, which requires that we only need to inherit the interface provided by the framework, and name the method according to the convention to complete the database operation we want.
The user database operation is as follows:

@Repository
public interface IUserDao extends JpaRepository<User,Integer> {

}

The shipping address works as follows:

@Repository
public interface IAddressDao extends JpaRepository<Address,Integer> {

}

As the reader can see, our DAO only needs to inherit JpaRepository, it has already helped us complete the basic CURD and other operations, if you want to know more about this project of spring-data, please refer to the official document of spring, it Than do not plan our study of anomalies.

Service exception design

ok, finally to our point, we have to complete some of the service operations: add a delivery address, delete a delivery address, get a list of delivery addresses.
First look at my service interface definition:

public interface IAddressService {

/**
 * 创建收货地址
 * @param uid
 * @param address
 * @return
 */
Address createAddress(Integer uid,Address address);

/**
 * 删除收货地址
 * @param uid
 * @param aid
 */
void deleteAddress(Integer uid,Integer aid);

/**
 * 查询用户的所有收货地址
 * @param uid
 * @return
 */
List<Address> listAddresses(Integer uid);
}

Let's focus on the implementation:

Add shipping address

First, let's look at the constraints that were sorted out earlier:

Intake:

  1. userid
  2. Shipping address entity information

constraint:

  1. The user id cannot be empty, and the user does exist
  2. Required fields for shipping address cannot be empty
  3. If the user does not have a shipping address, set it as the default shipping address when the shipping address is created

First look at the following code implementation:

 @Override
public Address createAddress(Integer uid, Address address) {
    //============ 以下为约束条件   ==============
    //1.用户id不能为空,且此用户确实是存在的
    Preconditions.checkNotNull(uid);
    User user = userDao.findOne(uid);
    if(null == user){
        throw new RuntimeException("找不到当前用户!");
    }
    //2.收货地址的必要字段不能为空
    BeanValidators.validateWithException(validator, address);
    //3.如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址
    if(ObjectUtils.isEmpty(user.getAddresses())){
        address.setIsDefault(true);
    }

    //============ 以下为正常执行的业务逻辑   ==============
    address.setUser(user);
    Address result = addressDao.save(address);
    return result;
}

Among them, the three constraints described above have been completed. When the three constraints are satisfied, normal business logic can be performed, otherwise an exception will be thrown (generally, it is recommended to throw a runtime exception here - RuntimeException) .

Introduce the following techniques I used above:

  1. Preconfitions.checkNotNull(T t) This is judged by using com.google.common.base.Preconditions in Guava. Because there are many verifications used in service, it is recommended to change Preconfitions to static import:

    import static com.google.common.base.Preconditions.checkNotNull;    
    

    Of course, the instructions in Guava's github also suggest that we use it this way.

  2. BeanValidators.validateWithException(validator, address);
    This is done using the jsr 303 specification implemented by hibernate. It needs to pass in a validator and an entity that needs to be validated, so how does the validator get it, as follows:

    @Configuration
    public class BeanConfigs {

    @Bean
    public javax.validation.Validator getValidator(){
        return new LocalValidatorFactoryBean();
    }
    

    }

He will get a Validator object, and then we can use it by injecting it in the service:

 @Autowired     
private Validator validator ;

So how is the BeanValidators class implemented? In fact, the implementation method is very simple, as long as it is ok to judge the annotation of jsr 303.
So where are the annotations of jsr 303 written? Of course, it is written in the address entity class:

@Entity
@Setter
@Getter
public class Address {
@Id
    @GeneratedValue
    private Integer id;
    @NotNull
private String province;//省
@NotNull
private String city;//市
@NotNull
private String county;//区
private Boolean isDefault = false;//是否是默认地址

@ManyToOne(cascade={CascadeType.ALL})
@JoinColumn(name="uid")
private User user;
}

Write the constraints you need to make judgments. If it is reasonable, you can perform business operations to operate the database.
The verification of this block is necessary for one of the main reasons: such verification can avoid the insertion of dirty data. If readers have the experience of the official launch, they can understand such a thing, any code error can be tolerated and modified, but if there is a dirty data problem, then it may be a devastating disaster. Program problems can be corrected, but the appearance of dirty data may not be recoverable. So this is why it is necessary to judge the constraints in the service and then perform business logic operations.

  1. The judgment here is a business logic judgment, which is a screening judgment from a business perspective. In addition, there may be different business conditions in many scenarios, and you only need to do it according to the requirements.

A summary of the constraints is as follows:

  • Basic judgment constraints (basic judgments such as null values)
  • Entity attribute constraints (to meet basic judgments such as jsr 303)
  • Business condition constraints (different business constraints proposed by requirements)

When these three points are satisfied, the next step can be carried out.

ok, it basically introduces how to make a basic judgment, then back to the design of exceptions, the above code has clearly described how to reasonably judge an exception in an appropriate position, so how to throw an exception reasonably?
Does throwing only RuntimeException count as graceful throwing? Of course not. For exceptions thrown in service, I think there are roughly two ways to throw them:

  1. Throws an exception with status code RumtimeException
  2. Throws a RuntimeException of the specified type

Compared with these two exception methods, the first exception refers to that all my exceptions throw RuntimeException exceptions, but they need to bring a status code. The caller can check what kind of service throws them according to the status code. exception.
The second type of exception refers to what kind of exception is thrown in the service, customize a specified exception error, and then throw the exception in progress.
Generally speaking, if the system has no other special requirements, it is recommended to use the second method in the development and design. But for example, for exceptions like basic judgment, you can use the class library provided by guava to operate. jsr 303 exceptions can also be operated using their own encapsulated exception judgment classes, because these two exceptions are basic judgments, and no special exceptions need to be specified for them. But for the exception thrown by the third obligation condition constraint judgment, it is necessary to throw the specified type of exception.
for

throw new RuntimeException("找不到当前用户!");

Define a specific exception class to perform the judgment of this obligation exception:

public class NotFindUserException extends RuntimeException {
public NotFindUserException() {
    super("找不到此用户");
}

public NotFindUserException(String message) {
    super(message);
}
}

Then change this to:

throw new NotFindUserException("找不到当前用户!");

or

throw new NotFindUserException();

ok, through the above modifications to the service layer, the code changes are as follows:

@Override
public Address createAddress(Integer uid, Address address) {
    //============ 以下为约束条件   ==============
    //1.用户id不能为空,且此用户确实是存在的
    checkNotNull(uid);
    User user = userDao.findOne(uid);
    if(null == user){
        throw new NotFindUserException("找不到当前用户!");
    }
    //2.收货地址的必要字段不能为空
    BeanValidators.validateWithException(validator, address);
    //3.如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址
    if(ObjectUtils.isEmpty(user.getAddresses())){
        address.setIsDefault(true);
    }

    //============ 以下为正常执行的业务逻辑   ==============
    address.setUser(user);
    Address result = addressDao.save(address);
    return result;
}

Such a service seems to be more stable and understandable.

Delete shipping address:

Intake:

  1. userid
  2. delivery address id

constraint:

  1. The user id cannot be empty, and the user does exist
  2. The delivery address cannot be empty, and the delivery address does exist
  3. Determine whether this delivery address is the user's delivery address
  4. Determine whether this shipping address is the default shipping address, if it is the default shipping address, it cannot be deleted

It is similar to adding the delivery address above, so I won't repeat it. The delete service is designed as follows:

@Override
public void deleteAddress(Integer uid, Integer aid) {
    //============ 以下为约束条件   ==============
    //1.用户id不能为空,且此用户确实是存在的
    checkNotNull(uid);
    User user = userDao.findOne(uid);
    if(null == user){
        throw new NotFindUserException();
    }
    //2.收货地址不能为空,且此收货地址确实是存在的
    checkNotNull(aid);
    Address address = addressDao.findOne(aid);
    if(null == address){
        throw new NotFindAddressException();
    }
    //3.判断此收货地址是否是用户的收货地址
    if(!address.getUser().equals(user)){
        throw new NotMatchUserAddressException();
    }
    //4.判断此收货地址是否为默认收货地址,如果是默认收货地址,那么不能进行删除
    if(address.getIsDefault()){
       throw  new DefaultAddressNotDeleteException();
    }

    //============ 以下为正常执行的业务逻辑   ==============
    addressDao.delete(address);
}

Four related exception classes are designed: NotFindUserException, NotFindAddressException, NotMatchUserAddressException, DefaultAddressNotDeleteException. Different exceptions are thrown according to different business requirements.

Get a list of shipping addresses:

Intake:

  1. userid

constraint:

  1. The user id cannot be empty, and the user does exist

code show as below:

 @Override
public List<Address> listAddresses(Integer uid) {
    //============ 以下为约束条件   ==============
    //1.用户id不能为空,且此用户确实是存在的
    checkNotNull(uid);
    User user = userDao.findOne(uid);
    if(null == user){
        throw new NotFindUserException();
    }

    //============ 以下为正常执行的业务逻辑   ==============
    User result = userDao.findOne(uid);
    return result.getAddresses();
}

API exception design

There are roughly two ways to throw:

  1. Throws an exception with status code RumtimeException
  2. Throws a RuntimeException of the specified type

This is mentioned when designing the service layer exception. Through the introduction of the service layer, we choose the second method of throwing when the service layer throws an exception. The difference is that we need to use the api layer to throw an exception. There are two ways to throw: specify the type of api exception, and specify the relevant status code, and then throw the exception. The core of this exception design is to make the user who calls the api more clearly aware of the occurrence of the exception In addition to throwing an exception, we also need to make a corresponding table for the detailed information of the exception corresponding to the status code and the possible problems of the exception and display it to the user, which is convenient for the user to query. (such as the api document provided by github, the api document provided by WeChat, etc.), there is another advantage: if the user needs to customize the prompt message, he can modify the prompt according to the returned status code.

api validation constraints

First of all, for the design of the api, there needs to be a dto object. This object is responsible for communicating and transmitting data with the caller, and then dto->domain is passed to the service for operation. This must be noted. The second point , in addition to the basic judgment (null judgment) and jsr 303 verification of the service mentioned, the API layer also needs to perform related verification. If the verification fails, it will be returned directly to the caller to inform the call that the call failed and should not be brought If you want to access the service with illegal data, then the reader may be a little confused. If the service has not been verified, why does the API layer still need to be verified? Here is a concept designed: Murphy's Law in programming, if the data verification at the api layer is neglected, it is possible that illegal data will be brought to the service layer, and then the dirty data will be saved to the database.

So the core of careful programming is: never trust that the data you receive is legitimate.

API exception design

When designing api layer exceptions, as we said above, you need to provide error codes and error information, then you can design in this way to provide a general api superclass exception, and other different api exceptions inherit from this superclass:

public class ApiException extends RuntimeException {
protected Long errorCode ;
protected Object data ;

public ApiException(Long errorCode,String message,Object data,Throwable e){
    super(message,e);
    this.errorCode = errorCode ;
    this.data = data ;
}

public ApiException(Long errorCode,String message,Object data){
    this(errorCode,message,data,null);
}

public ApiException(Long errorCode,String message){
    this(errorCode,message,null,null);
}

public ApiException(String message,Throwable e){
    this(null,message,null,e);
}

public ApiException(){

}

public ApiException(Throwable e){
    super(e);
}

public Long getErrorCode() {
    return errorCode;
}

public void setErrorCode(Long errorCode) {
    this.errorCode = errorCode;
}

public Object getData() {
    return data;
}

public void setData(Object data) {
    this.data = data;
}

}

Then define api layer exceptions: ApiDefaultAddressNotDeleteException, ApiNotFindAddressException, ApiNotFindUserException, ApiNotMatchUserAddressException.
Take the default address that cannot be deleted as an example:

public class ApiDefaultAddressNotDeleteException extends ApiException {

public ApiDefaultAddressNotDeleteException(String message) {
    super(AddressErrorCode.DefaultAddressNotDeleteErrorCode, message, null);
}

}

AddressErrorCode.DefaultAddressNotDeleteErrorCode is the error code that needs to be provided to the caller. The error code classes are as follows:

public abstract class AddressErrorCode {
    public static final Long DefaultAddressNotDeleteErrorCode = 10001L;//默认地址不能删除
    public static final Long NotFindAddressErrorCode = 10002L;//找不到此收货地址
    public static final Long NotFindUserErrorCode = 10003L;//找不到此用户
    public static final Long NotMatchUserAddressErrorCode = 10004L;//用户与收货地址不匹配
}

ok, then the exception of the api layer has been designed. Let me say one more thing here. The AddressErrorCode error code class stores the possible error codes. It is more reasonable to put it in the configuration file for management.

api handles exceptions

The api layer will call the service layer, and then handle all the exceptions that appear in the service. First of all, it is necessary to ensure that the api layer must be very light, basically making it a forwarding function (interface parameters, passed to the service parameters, Return data to the caller, these three basic functions), and then perform exception handling on the method call passed to the service parameter.

Here is just an example of adding an address:

 @Autowired
private IAddressService addressService;


/**
 * 添加收货地址
 * @param addressDTO
 * @return
 */
@RequestMapping(method = RequestMethod.POST)
public AddressDTO add(@Valid @RequestBody AddressDTO addressDTO){
    Address address = new Address();
    BeanUtils.copyProperties(addressDTO,address);
    Address result;
    try {
        result = addressService.createAddress(addressDTO.getUid(), address);
    }catch (NotFindUserException e){
        throw new ApiNotFindUserException("找不到该用户");
    }catch (Exception e){//未知错误
        throw new ApiException(e);
    }
    AddressDTO resultDTO = new AddressDTO();
    BeanUtils.copyProperties(result,resultDTO);
    resultDTO.setUid(result.getUser().getId());

    return resultDTO;
}

The solution here is to judge the type of exception when calling the service, then convert any service exception into an api exception, and then throw an api exception, which is a commonly used exception conversion method. Similarly, deleting the delivery address and obtaining the delivery address are also handled in the same way, and will not be repeated here.

api abnormal conversion

I have explained how to throw exceptions and how to convert service exceptions into api exceptions. Does the exception handling be completed by directly throwing them into api exceptions? The answer is no, when an api exception is thrown, we need to make the data returned by the api exception (json or xml) for the user to understand, then we need to convert the api exception into a dto object (ErrorDTO), see the following code:

@ControllerAdvice(annotations = RestController.class)

class ApiExceptionHandlerAdvice {

/**
 * Handle exceptions thrown by handlers.
 */
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseEntity<ErrorDTO> exception(Exception exception,HttpServletResponse response) {
    ErrorDTO errorDTO = new ErrorDTO();
    if(exception instanceof ApiException){//api异常
        ApiException apiException = (ApiException)exception;
        errorDTO.setErrorCode(apiException.getErrorCode());
    }else{//未知异常
        errorDTO.setErrorCode(0L);
    }
    errorDTO.setTip(exception.getMessage());
    ResponseEntity<ErrorDTO> responseEntity = new ResponseEntity<>(errorDTO,HttpStatus.valueOf(response.getStatus()));
    return responseEntity;
}

@Setter
@Getter
class ErrorDTO{
    private Long errorCode;
    private String tip;
}

}

ok, this completes the conversion of api exceptions into DTO objects that users can understand. @ControllerAdvice is used in the code, which is a special aspect processing provided by spring MVC.

When an exception occurs when calling the api interface, the user can also receive the normal data format. For example, when there is no user (uid is 2), the delivery address is added for this user. After postman (Google plugin is used to simulate http requests) The data:

{
  "errorCode": 10003,
  "tip": "找不到该用户"
}

Summarize

This article only focuses on how to design exceptions. The api transmission and service processing involved need to be optimized. For example, api interface access needs to be encrypted with https, api interface needs OAuth2.0 authorization, or api interface needs signature authentication and other issues The focus of this article is how to deal with exceptions, so readers only need to pay attention to the problems and processing methods related to exceptions. Hope this article helps you understand exceptions.

Those with 1-5 work experience, who do not know where to start in the face of the current popular technology, and who need to break through the technical bottleneck can join the group. After staying in the company for a long time, I lived very comfortably, but the interview hit a wall when I changed jobs. Those who need to study in a short period of time and change jobs to get high salaries can join the group. If you have no work experience, but have a solid foundation, you can join the group if you are proficient in the working mechanism of java, common design ideas, and common java development frameworks. java architecture group: 697579751 to communicate together


Author: JDX Love Learning
Link: https://juejin.im/post/5a93bb835188257a7b5ab9a4
Source: Nuggets
The copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324538418&siteId=291194637