Mp.weixin.qq.com/s/llpnteX2Z…

Introduction:

Exception processing is one of the essential operations in the program development, but how to correctly and elegantly handle the exception is really a knowledge, the author according to their own development experience to talk about how I am to deal with the exception. Since this article is only about experience and does not cover the basics, if the reader is still confused about the concept of exceptions, please check the basics first.

How do I choose an exception type

Categories of exceptions

As we all know, The Exception superclass in Java is java.lang.throwable, which has two important subclasses, Java.lang. Exception and java.lang.Error Or), where errors are managed by the JVM, such as outofMemoryErrors, which are known to us, so we will not focus on Error exceptions in this article, so we will talk about exceptions in detail. Exception has an important subclass called RuntimeException. We refer to RuntimeException or other subclasses that inherit from RuntimeException as unexamined Exception, and other subclasses that inherit from Exception as checked Exception. This article focuses on two types of anomalies, detected and undetected.

How to select exceptions

From the author’s development experience, if in an application, you need to develop a method (such as a function of the service method), the method may appear if the exception, so you need to consider whether the anomaly appeared after the caller can handle, and if you want to call for processing, if the caller can handle, And you also hope that the caller for processing, then throw exceptions, tested was to remind the caller when using your method, if handled when considering if an exception is thrown, similarly, in writing if a method, you think it’s a accident is unusual, in theory, what do you think of runtime may encounter problems, and these problems may not be inevitable, There is no need for the caller to display an exception to determine the operation of the business process, so a non-checked exception like RuntimeException can be used instead. Well, I guess I said above this paragraph, you read many times also still feel obscure. So, please follow my train of thought, slowly understand.

When do I need to throw an exception

The first thing we need to know is when do we need to throw an exception? The design of exceptions is convenient for developers to use, but it is not indiscriminate. The author also asked a lot of friends about when to throw exceptions, but few of them can give an accurate answer. The problem is simple: if you feel that some “problem” can’t be solved, you can throw an exception. For example, if you’re writing a service and you’re writing some code that might cause problems, throw an exception, and trust me, this is the best time to throw an exception.

What kind of exception should be thrown

Now that we know when we need to throw an exception, we can ask ourselves, when we do throw an exception, what kind of exception should we use? Is it a checked exception or an unchecked exception? Let me illustrate this problem, first from exceptions, such as there is a business logic, need to read some data from a file, the read operation may be caused by other problems such as the files are deleted unable to get to read mistakes, then from redis or mysql database again to get this data, refer to the following code, GetKey (Integer) is the entry program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public
String getKey(Integer key){

String value;

try
{

InputStream inputStream = getFiles(
"/file/nofile"
);

// Then read the value of the key from the stream

value = ... ;

}
catch
(Exception e) {

// If an exception is thrown, it will be fetched from mysql or 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 read error"
,e.getCause());

}

return
inputStream;
}

Ok, after looking at the above code, you may have some ideas in mind, originally checked exceptions can control the obligation logic, yes, yes, through checked exceptions can really control the business logic, but remember not to use this, we should reasonably throw exceptions, because the program itself is the process, Abnormal function is only when you do not find an excuse, it does not as a control program flow entry or exit, if such use, is a function of the abnormal enlargement, this will lead to increased code complexity, coupling increases, reduce problems such as code readability. So should you never use such an exception? In fact, it is not, when there is really such a need, we can use it, just remember, do not really use it as a tool or means to control the process. So when exactly do you throw an exception like this? We will only consider using checked exceptions if the caller needs to handle the error if the call goes wrong.

Now, let’s take a look at runtimeExceptions, which we see a lot, Such as Java. Lang. NullPointerException/Java. Lang. IllegalArgumentException, etc., so this exception when we throw? When we write a method that might accidentally met one mistake, we think this problem is likely to occur when running, and theoretically, do not have this problem, the program will perform well, it does not force the caller must catch this exception, at this time to throw RuntimeException exceptions, for example, When a path is passed, we need to return the File object corresponding to the path:

1
2
3
4
5
6
7
8
9
10
11
12
public
void
test() {

myTest.getFiles(
""
);
}
public
File getFiles(String path) {

if
(
null
== path ||
""
.equals(path)){

throw
new
NullPointerException(
"Paths cannot be empty!"
);

}

File file =
new
File(path);

return
file;
}

As the above example shows, if path is empty when the caller calls getFiles(String), then the null pointer exception (which is a subclass of RuntimeException) is thrown. The caller does not have to explicitly try… The catch… Operation to force processing. This requires the caller to validate before calling such a method to avoid runtimeexceptions. As follows:

Which exception should be selected

From the above description and examples, we can conclude that the difference between a RuntimeException and a checked exception is whether it is mandatory for the caller to handle the exception, and if it is mandatory for the caller to handle the exception, the checked exception is used. Otherwise, select RuntimeException. In general, if there are no special requirements, we recommend using RuntimeException.

Scenario introduction and technology selection

Architectural description

As we all know, traditional projects are developed on the basis of the MVC framework. This article focuses on the use of restful interface design to experience the elegance of exception handling. Let’s focus on the restful API layer (similar to the Controller layer on the Web) and the Service layer to see how exceptions are thrown in the Service and then caught and translated by the API layer. The technologies used are: Spring-boot, JPA (Hibernate),mysql. If you are not familiar with these technologies, you need to read the relevant materials by yourself.

Service Scenario Description

Choose a relatively simple business scenario, the shipping address management in the electricity, for example, a user in the mobile terminal to buy goods, need to manage the shipping address, in the project, provide some access to mobile API interface, such as: add a shipping address, delete shipping address, change the goods address, set the default shipping address, shipping address list query, Single receiving address query interface.

Construction constraints

Ok, this is a very basic business scenario set up, of course, no matter what the API operation, it contains some rules:

Add shipping address:

  • The user id

  • Receiving address entity information

Constraints:

  • The user ID cannot be empty and does exist

  • The required fields of the shipping address cannot be empty

  • If the user does not already have a shipping address, set this shipping address to the default when it is created –

Delete shipping address:

  • The user id

  • Shipping address ID

Constraints:

  • The user ID cannot be empty and does exist

  • The shipping address cannot be empty and does exist

  • Determine if this shipping address is the user’s shipping address

  • Check whether this shipping address is the default shipping address. If it is the default shipping address, you cannot delete it

Change of shipping address:

  • The user id

  • Shipping address ID

Constraints:

  • The user ID cannot be empty and does exist

  • The shipping address cannot be empty and does exist

  • Determine if this shipping address is the user’s shipping address

Default address setting: Input parameter:

  • The user id

  • Shipping address ID

Constraints:

  • The user ID cannot be empty and does exist

  • The shipping address cannot be empty and does exist

  • Determine if this shipping address is the user’s shipping address

Shipping address list query: Input parameter:

  • The user id

Constraints:

  • The user ID cannot be empty and does exist

Single delivery address query: input parameter:

  • The user id

  • Shipping address ID

Constraints:

  • The user ID cannot be empty and does exist

  • The shipping address cannot be empty and does exist

  • Determine if this shipping address is the user’s shipping address

Constraint judgment and technology selection

For the list of constraints and functions listed above, I chose a few typical exception handling scenarios: add a receiving address, delete a receiving address, and get a list of receiving addresses. So we should have what necessary knowledge reserve, let’s take a look at the function of the receiving address: add the receiving address need to verify the user ID and the receiving address entity information, so for the judgment of non-empty, how do we choose the tool? The conventional judgment is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
/ * *

* Add address

* @param uid

* @param address

* @return

* /
public
Address addAddress(Integer uid,Address address){

if
(
null
! = uid){

// process...

}

return
null
;
}

In the example above, it would be fine if uid were null, but if some necessary attributes in the address entity were null, it would be disastrous if there were many fields. So how should we do the judgment of these input parameters? Let’s introduce two points of knowledge:

  1. The Preconditions class in Guava implements many of the input method judgments

  2. The Validation specification of JSR 303 (currently the hibernate implementation’s hibernate-Validator) makes entry validation much easier if you use these two recommendation techniques. It is recommended that you use these mature technologies and JAR toolkits, which can reduce a lot of unnecessary work. We just need to focus on the business logic. And it won’t take any more time because of these input judgments.

How to elegantly design Java exceptions

Domain is introduced

Depending on the project scenario, you need two domain models, one for the user entity and one for the address entity. Address domain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
@Data
public
class
Address {

@Id

@GeneratedValue

private
Integer id;

private
String province;
/ / province

private
String city;
/ / the city

private
String county;
/ / area

private
Boolean isDefault;
// Is the default address

@ManyToOne
(cascade={CascadeType.ALL})

@JoinColumn
(name=
"uid"
)

private
User user;
}

User domains are as follows:

1
2
3
4
5
6
7
8
9
10
11
@Entity
@Data
public
class
User {

@Id

@GeneratedValue

private
Integer id;

private
String name;
/ / name

@OneToMany
(cascade= CascadeType.ALL,mappedBy=
"user"
,fetch = FetchType.LAZY)

private
Set<Address> addresses;
}

Ok, this is a model relationship. The user-shipping address relationship is 1-n. The @data feature uses a tool called Lombok, which automatically generates setters and getters and other methods.

The dao is introduced

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 according to the convention to name the method, we can complete the database operations we want. User database operations are as follows:

1
2
3
4
@Repository
public
interface
IUserDao
extends
JpaRepository<User,Integer> {
}

The delivery address operation is as follows:

1
2
3
4
@Repository
public
interface
IAddressDao
extends
JpaRepository<Address,Integer> {
}

As you can see, our DAO only needs to inherit from JpaRepository, which already does the basic CURD and other operations for us. To learn more about the spring-data project, please refer to the official Spring documentation, which does not cover our investigation of exceptions.

Service Exception Design

Ok, here we go. We need to complete the parts of service: add shipping address, delete shipping address, and get the list of shipping addresses. Let’s start with my service interface definition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public
interface
IAddressService {
/ * *

* Create a shipping address

* @param uid

* @param address

* @return

* /
Address createAddress(Integer uid,Address address);
/ * *

* Delete shipping address

* @param uid

* @param aid

* /
void
deleteAddress(Integer uid,Integer aid);
/ * *

* Query all shipping addresses of users

* @param uid

* @return

* /
List<Address> listAddresses(Integer uid);
}

Let’s focus on the implementation:

Add a shipping address

First of all, let’s take a look at the previously sorted constraints:

The arguments:

  • The user id

  • Receiving address entity information

Constraints:

  • The user ID cannot be empty and does exist

  • The required fields of the shipping address cannot be empty

  • If the user does not already have a shipping address, set this shipping address to the default when it is created

Let’s start with the following code implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public
Address createAddress(Integer uid, Address address) {

//============ The following constraints are ==============

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

Preconditions.checkNotNull(uid);

User user = userDao.findOne(uid);

if
(
null
== user){

throw
new
RuntimeException(
"Current user not found!"
);

}

//2. The required fields of the shipping address must not be empty

BeanValidators.validateWithException(validator, address);

//3. If the user does not already have a shipping address, set this shipping address to the default when it is created

if
(ObjectUtils.isEmpty(user.getAddresses())){

address.setIsDefault(
true
);

}

//============ The following is the normal service logic ==============

address.setUser(user);

Address result = addressDao.save(address);

return
result;
}

When the three constraints described above are met, normal business logic can proceed; otherwise, an exception (RuntimeException is generally recommended here) will be thrown.

Here are some of the techniques I used:

  • Preconfitions. CheckNotNull (T T) this is the use of Guava the com.google.com mon. Base. The Preconditions for judgment, because of the large used in the validation of the service, Therefore, you are advised to change Preconfitions to static import mode:

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

Of course, Guava’s Github notes also suggest this.

  • BeanValidators.validateWithException(validator, address);

This is done using hibernate’s JSR 303 specification, which requires passing in a Validator and an entity to validate.

1
2
3
4
5
6
7
8
@Configuration
public
class
BeanConfigs {
@Bean
public
javax.validation.Validator getValidator(){

return
new
LocalValidatorFactoryBean();
}
}

It will get a Validator object, which we can inject into the service and use:

1
2
@Autowired
private
Validator validator ;

So how is the BeanValidators class implemented? In fact, the implementation is very simple, just to judge the ANNOTATIONS of JSR 303. So where are the annotations for JSR 303? In the address entity class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
@Setter
@Getter
public
class
Address {
@Id

@GeneratedValue

private
Integer id;

@NotNull
private
String province;
/ / province
@NotNull
private
String city;
/ / the city
@NotNull
private
String county;
/ / area
private
Boolean isDefault =
false
;
// Is the default address
@ManyToOne
(cascade={CascadeType.ALL})
@JoinColumn
(name=
"uid"
)
private
User user;
}

Write down the constraints you need to judge, and if it makes sense, then you can do business, and then operate on the database. This validation is necessary for one major reason: it avoids the insertion of dirty data. If you have any real experience, you can understand that any code bugs can be tolerated and fixed, but if you have a dirty data problem, it can be a devastating disaster. Problems with the program can be fixed, but the presence of dirty data may not be recoverable. This is why it is important to determine the constraints before performing business logic operations in a service.

  1. The judgment here is business logic judgment, which is filtered from the business perspective. In addition, there may be different business conditions in many scenarios, so you just need to follow the requirements.

The constraints are summarized as follows:

  • Basic judgment constraints (null values, etc.)

  • Entity attribute constraints (satisfy basic judgments such as JSR 303)

  • Business constraints (different business constraints on requirements)

When the three points are met, you can proceed to the next step

Ok, basically introduced how to make a basic judgment, so back to the exception design problem, the above code has clearly described how to determine an exception in the appropriate place, so how to throw an exception reasonably? Is throwing only RuntimeException considered elegant? Of course not. There are roughly two ways to throw an exception in a service:

  1. Throw RumtimeException with status code

  2. Throws a RuntimeException of the specified type

In contrast to these two exceptions, the first exception means that all my exceptions throw runtimeExceptions, but with a status code, the caller can use the status code to see what kind of exception the service threw. The second type of exception is to define a specified exception error for any exception thrown in the service, and then throw the exception. Generally speaking, if the system has no other special requirements, in the development design, it is recommended to use the second method. But exceptions like basic judgment, for example, can be manipulated entirely using the library that Guava provides us. JSR 303 exceptions can also be operated on using their own wrapped exception judgment classes, because both exceptions are base judgments and no special exceptions need to be specified for them. However, for the third obligation condition to determine the exception to be thrown, it is necessary to throw an exception of the specified type. for

1
throw
new
RuntimeException(
"Current user not found!"
);

Define a specific exception class for this obligatory exception determination:

1
2
3
4
5
6
7
8
9
public
class
NotFindUserException
extends
RuntimeException {
public
NotFindUserException() {

super
(
"This user could not be found"
);
}
public
NotFindUserException(String message) {

super
(message);
}
}

Then change this to:

1
throw
new
NotFindUserException(
"Current user not found!"
);

or

1
throw
new
NotFindUserException();

Ok, with the above changes to the service layer, the code changes as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public
Address createAddress(Integer uid, Address address) {

//============ The following constraints are ==============

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

checkNotNull(uid);

User user = userDao.findOne(uid);

if
(
null
== user){

throw
new
NotFindUserException(
"Current user not found!"
);

}

//2. The required fields of the shipping address must not be empty

BeanValidators.validateWithException(validator, address);

//3. If the user does not already have a shipping address, set this shipping address to the default when it is created

if
(ObjectUtils.isEmpty(user.getAddresses())){

address.setIsDefault(
true
);

}

//============ The following is the normal service logic ==============

address.setUser(user);

Address result = addressDao.save(address);

return
result;
}

This makes the service seem more stable and understandable.

Delete shipping address:

The arguments:

  • The user id

  • Shipping address ID

Constraints:

  • The user ID cannot be empty and does exist

  • The shipping address cannot be empty and does exist

  • Determine if this shipping address is the user’s shipping address

  • Check whether this shipping address is the default shipping address. If it is the default shipping address, you cannot delete it

The service design of delete is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public
void
deleteAddress(Integer uid, Integer aid) {

//============ The following constraints are ==============

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

checkNotNull(uid);

User user = userDao.findOne(uid);

if
(
null
== user){

throw
new
NotFindUserException();

}

//2. The shipping address cannot be empty and does exist

checkNotNull(aid);

Address address = addressDao.findOne(aid);

if
(
null
== address){

throw
new
NotFindAddressException();

}

//3. Check whether the shipping address is the user's shipping address

if
(! address.getUser().equals(user)){

throw
new
NotMatchUserAddressException();

}

//4. Check whether the address is the default address. If it is the default address, you cannot delete it

if
(address.getIsDefault()){

throw
new
DefaultAddressNotDeleteException();

}

//============ The following is the normal service logic ==============

addressDao.delete(address);
}

Design the related four exception class: NotFindUserException, NotFindAddressException, NotMatchUserAddressException, DefaultAddressNotDeleteException. Different exceptions are thrown based on different business requirements.

Get a list of shipping addresses:

The arguments:

  • The user id

Constraints:

  • The user ID cannot be empty and does exist

The code is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public
List<Address> listAddresses(Integer uid) {

//============ The following constraints are ==============

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

checkNotNull(uid);

User user = userDao.findOne(uid);

if
(
null
== user){

throw
new
NotFindUserException();

}

//============ The following is the normal service logic ==============

User result = userDao.findOne(uid);

return
result.getAddresses();
}

API exception Design

There are roughly two ways to throw:

  1. Throw RumtimeException with status code

  2. Throws a RuntimeException of the specified type

This is mentioned in the design of the service layer exception. By introducing the service layer exception, we choose the second method to throw the exception. The difference is that we need to use the two methods to throw the exception in the API layer: to specify the TYPE of API exception, and to specify the related status code. Before the exception is thrown, the exception design is the core of calling the API so users can more clearly understand the details of an exception occurs, in addition to throw an exception, we will also need a status code corresponding to the detailed information, and anomaly likely problem into a corresponding table display to the user, convenient for the user’s query. (such as API documents provided by Github, API documents provided by wechat, etc.), there is another advantage: if the user needs to customize the prompt message, the prompt can be modified according to the returned status code.

API validation constraints

First of all, for the design of the API, there should be a DTO object, which is responsible for the communication and transmission of data with the caller, and then the DTO ->domain is transmitted to the service for operation, which must be noted. Second, In addition to null validation and JSR 303 validation, the API layer also needs to perform related validation. If the validation fails, the API layer directly returns to the caller and tells the caller that the call failed. The service should not be accessed with invalid data. The reader may wonder why the API layer needs validation when the Service already does it. The concept here is murphy’s Law of programming: if the API layer fails to validate data, it is possible that illegal data will be brought to the service layer, which in turn saves dirty data to the database.

So the core of careful programming is this: Never believe that the data you receive is legitimate.

API exception Design

When designing API layer exceptions, as we mentioned above, we need to provide error codes and error messages, so we can design to provide a generic API superclass exception from which all other different API exceptions inherit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 separately: ApiDefaultAddressNotDeleteException ApiNotFindAddressException, ApiNotFindUserException ApiNotMatchUserAddressException. For example, the default address cannot be deleted:

1
2
3
4
5
6
public
class
ApiDefaultAddressNotDeleteException
extends
ApiException {
public
ApiDefaultAddressNotDeleteException(String message) {

super
(AddressErrorCode.DefaultAddressNotDeleteErrorCode, message,
null
);
}
}

AddressErrorCode. DefaultAddressNotDeleteErrorCode is the need to provide the error code to the caller. Error codes are as follows:

1
2
3
4
5
6
public
abstract
class
AddressErrorCode {

public
static
final
Long DefaultAddressNotDeleteErrorCode = 10001L;
// The default address cannot be deleted

public
static
final
Long NotFindAddressErrorCode = 10002L;
// This shipping address cannot be found

public
static
final
Long NotFindUserErrorCode = 10003L;
// The user could not be found

public
static
final
Long NotMatchUserAddressErrorCode = 10004L;
// The user does not match the shipping address
}

The AddressErrorCode error class stores possible error codes, and it makes more sense to manage them in a configuration file.

API handling exception

The API layer will call the Service layer and handle all exceptions that occur in the service. First, make sure that the API layer is very light and basically a forwarding function (interface parameters, passing parameters to the service, returning data to the caller). Exception handling is then done on the method call passed to the service parameter.

Here is an example for adding an IP address:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Autowired
private
IAddressService addressService;
/ * *

* Add shipping address

* @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(
"The user could not be found"
);

}
catch
(Exception e){
// Unknown error

throw
new
ApiException(e);

}

AddressDTO resultDTO =
new
AddressDTO();

BeanUtils.copyProperties(result,resultDTO);

resultDTO.setUid(result.getUser().getId());

return
resultDTO;
}

The solution here is to call the service, determine the type of exception, and then convert any service exception into an API exception, and then throw an API exception. This is a common method of exception conversion. Similarly deleting the shipping address and obtaining the shipping address are similarly treated, which will not be described here.

API exception conversion

Now that we’ve explained how to throw an exception and how to convert a service exception into an API exception, does that complete exception handling? The answer is no, when thrown API abnormal, we need to put the API data of abnormal return (json or XML) allow the user to understand, you need to convert API exception to dto objects (ErrorDTO), see the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@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
{
// Unknown exception

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, now that the API exception has been converted into a DTO object that the user can read, the code uses @ControllerAdvice, a special aspect processing provided by Spring MVC.

The user can also receive the data in the normal format when an exception occurs when calling the API, such as adding a shipping address to a user when there is no user (uid = 2) :

1
2
3
4
{

"errorCode"
:
10003
.

"tip"
:
"The user could not be found"
}

conclusion

This paper only focuses on how to design exceptions. The API transmission and service processing involved need to be optimized. For example, THE API interface access needs HTTPS encryption, THE API interface needs OAuth2.0 authorization or the API interface needs signature authentication, etc., which have not been mentioned in the paper. The focus of this article is on how exceptions are handled, so focus only on the issues and handling of exceptions. Hopefully this article has helped you understand exceptions.