In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article mainly introduces how to elegantly design Java exception related knowledge, the content is detailed and easy to understand, the operation is simple and fast, has a certain reference value, I believe you will get something after reading this elegant design Java exception article, let's take a look at it.
When do you need to throw an exception?
First of all, we need to understand the question, when do we need to throw an exception? Abnormal design is convenient for developers to use, but it is not misused. The author also asked a lot of friends about when to throw an exception, and not many of them can give an accurate answer. In fact, the problem is very simple, if you think that some "problems" can not be solved, then you can throw an exception. For example, if you are writing a service and you find that there may be a problem when you write a piece of code, please throw an exception. Believe me, it would be the best time for you to throw an exception at this time.
What kind of exception should be thrown
After knowing when we need to throw an exception, let's think about the question, really, when we throw an exception, what kind of exception should we choose? Is it abnormal or unchecked (RuntimeException)? Let me give an example to illustrate this problem. Let's start with the checked exception. For example, there is such a business logic that needs to read a certain data from a file. The reading operation may be due to other problems such as the file being deleted, which may lead to a reading error. Then you have to obtain this data from the redis or mysql database. Refer to the following code, and getKey (Integer) is the entry program.
Public String getKey (Integer key) {
String value
Try {
InputStream inputStream = getFiles ("/ file/nofile")
/ / next read the value of 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 ("Icano read error", e.getCause ())
}
Return inputStream
}
Ok, after reading the above code, you may have some ideas in mind that the checked exception can control the obligation logic, yes, yes, you can really control the business logic through the checked exception, but remember not to use it this way, we should throw the exception reasonably, because the program itself is the process, and the function of the exception is just an excuse when you can't go on. It can not be used as the entrance or exit of the control program flow, if used in this way, it will enlarge the function of the exception, which will lead to the increase of code complexity, the improvement of coupling, the reduction of code readability and so on. So is it necessary not to use such exceptions? In fact, it is not, when there is such a need, we can use it in this way, just remember, do not use it as a tool or means to control the process. So when on earth will such an exception be thrown? Consider that if the caller makes an error, we must let the caller handle the error. Only when such a requirement is met will we consider using the checked exception.
Next, let's take a look at unchecked anomalies (RuntimeException). We actually see a lot of exceptions like RuntimeException, such as java.lang.NullPointerException/java.lang.IllegalArgumentException, etc., so when do we throw this exception? When we are writing a method, we may accidentally encounter an error. We think that this problem may occur at runtime, and theoretically, if the program will execute normally without this problem, it does not force the caller to catch the exception. At this time, a RuntimeException exception is thrown. For example, when a path is sent, 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 ("path cannot be empty!")
}
File file = new File (path)
Return file
}
The above example shows that if the caller calls getFiles (String) if path is empty, then a null pointer exception (which is a subclass of RuntimeException) is thrown, and the caller does not need to display to try. Catch... The operation is forced to be processed. This requires the caller to validate when calling such a method to avoid RuntimeException. It is as follows:
Which exception should be chosen?
From the above description and examples, we can conclude that the difference between a RuntimeException exception and a detected exception is whether the caller is required to handle the exception. If the caller is required to handle the exception, then the checked exception is used, otherwise the non-checked exception (RuntimeException) is selected. Generally speaking, if there are no special requirements, we recommend using RuntimeException exceptions.
Scene introduction and technology selection
Architecture description
As we know, traditional projects are developed based on the MVC framework, and this article mainly experiences the elegance of exception handling from the design using restful-style interfaces.
Let's focus on the api layer of restful (similar to the controller layer in web) and the service layer, and look at how the exception is thrown in service, and then how the api layer catches and converts the exception.
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. Take the receiving address management in e-commerce as an example. Users need to perform receiving address management when purchasing goods on the mobile end. In the project, some api interfaces are provided to the mobile end for access, such as adding receiving address, deleting receiving address, changing receiving address, setting default receiving address, querying the list of receiving addresses, querying a single receiving address and so on.
Build constraints
Ok, this is a very basic business scenario set up. Of course, no matter what kind of api operation it is, it contains some rules:
Add Shipping address:
Input parameters:
User id
Receiving address entity information
Constraints:
User id cannot be empty, and this user does exist
The required field of the receiving address cannot be empty.
If the user does not already have a shipping address, it is set to the default shipping address when this shipping address is created-
Delete ship to address:
Input parameters:
User id
Receiving address id
Constraints:
User id cannot be empty, and this user does exist
The receiving address cannot be empty, and this receiving address does exist
Determine whether this shipping address is the user's shipping address.
Determine whether this shipping address is the default shipping address. If it is the default shipping address, it cannot be deleted.
Change the receiving address:
Input parameters:
User id
Receiving address id
Constraints:
User id cannot be empty, and this user does exist
The receiving address cannot be empty, and this receiving address does exist
Determine whether this shipping address is the user's shipping address.
Default address settings:
Input parameters:
User id
Receiving address id
Constraints:
User id cannot be empty, and this user does exist
The receiving address cannot be empty, and this receiving address does exist
Determine whether this shipping address is the user's shipping address.
Receiving address list query:
Input parameters:
User id
Constraints:
User id cannot be empty, and this user does exist
Query for a single shipping address:
Input parameters:
User id
Receiving address id
Constraints:
User id cannot be empty, and this user does exist
The receiving address cannot be empty, and this receiving address does exist
Determine whether this shipping address is the user's shipping address.
Constraint judgment and technology selection
For the list of constraints and functions listed above, I choose several typical exception handling scenarios to analyze: add the receiving address, delete the receiving address, and get the receiving address list.
So what is the necessary knowledge reserve? let's take a look at the receiving address function:
Adding the receiving address requires checking the physical information of the user's id and the receiving address, so for the judgment of non-empty, how do we choose the tool? The traditional judgment is as follows:
/ * *
* add address
* @ param uid
* @ param address
* @ return
, /
Public Address addAddress (Integer uid,Address address) {
If (null! = uid) {
/ / deal with it.
}
Return null
}
In the above example, if you only judge that uid is empty, and if you determine whether some of the necessary attributes in the address entity are empty, it will be disastrous in the case of a large number of fields.
So how should we judge these participants? let me introduce you to two knowledge points:
The Preconditions class in Guava implements the judgment of many input methods.
Validation specification of jsr 303 (hibernate-validator implemented by hibernate is more fully implemented at present)
If these two recommended techniques are used, the judgment of input parameters will be much easier. It is recommended that you use more of these mature technologies and jar toolkits, which can reduce a lot of unnecessary work. We just need to focus on business logic. It will not waste more time because of the judgment of these parameters.
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.
Address domain is as follows:
@ Entity
@ Data
Public class Address {
@ Id
@ GeneratedValue
Private Integer id
Private String province;// province
Private String city;// city
Private String county;// area
Is private Boolean isDefault;// the default address?
@ 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;// name
@ OneToMany (cascade= CascadeType.ALL,mappedBy= "user", fetch = FetchType.LAZY)
Private Set addresses
}
Ok, the above is a model relationship, and the user-to-ship address relationship is 1Mui n. The @ Data above uses a tool called lombok, which automatically generates methods such as Setter and Getter, which is very convenient to use, and interested readers can learn for themselves.
Dao introduction
In 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 operation of the user database is as follows:
@ Repository
Public interface IUserDao extends JpaRepository {
}
The operation of the receiving address is as follows:
@ Repository
Public interface IAddressDao extends JpaRepository {
}
As readers can see, our DAO only needs to inherit JpaRepository, and it has helped us to complete the basic CURD and other operations. If you want to know more about this project of spring-data, please refer to the official documentation of spring, which does not plan our research on anomalies.
Service abnormal design
Ok, finally to our focus, we have to complete some of the operations of service: add the receiving address, delete the receiving address, and get the list of receiving addresses.
First of all, take a look at my service interface definition:
Public interface IAddressService {
/ * *
* create a shipping address
* @ param uid
* @ param address
* @ return
, /
Address createAddress (Integer uid,Address address)
/ * *
* Delete the shipping address
* @ param uid
* @ param aid
, /
Void deleteAddress (Integer uid,Integer aid)
/ * *
* query all receiving addresses of the user
* @ param uid
* @ return
, /
List listAddresses (Integer uid)
}
Let's look at the implementation:
Add Shipping address
First, let's take a look at the previously sorted constraints:
Input parameters:
User id
Receiving address entity information
Constraints:
User id cannot be empty, and this user does exist
The required field of the receiving address cannot be empty.
If the user does not already have a shipping address, it is set to the default shipping address when this shipping address is created
Let's first look at the following code implementation:
@ Override
Public Address createAddress (Integer uid, Address address) {
/ / the following are constraints =
/ / 1. User id cannot be empty, and this user does exist
Preconditions.checkNotNull (uid)
User user = userDao.findOne (uid)
If (null = = user) {
Throw new RuntimeException ("current user not found!")
}
/ / 2. The required field of the receiving address cannot be empty.
BeanValidators.validateWithException (validator, address)
/ / 3. If the user does not already have a shipping address, it is set to the default shipping address when this shipping address is created
If (ObjectUtils.isEmpty (user.getAddresses () {
Address.setIsDefault (true)
}
/ / = the following is the normally executed business logic =
Address.setUser (user)
Address result = addressDao.save (address)
Return result
}
Among them, the three-point constraints described above have been completed, and normal business logic can only be carried out when the three-point constraints are met, otherwise an exception will be thrown (it is generally recommended to throw a runtime exception-RuntimeException here).
Introduce the following techniques I have used above:
Preconfitions.checkNotNull (T t) this is judged by com.google.common.base.Preconditions in Guava. Because there are many verifications used in service, it is recommended to change Preconfitions to static import:
1import static com.google.common.base.Preconditions.checkNotNull
Of course, the instructions in Guava's github suggest that we use them as well.
BeanValidators.validateWithException (validator, address)
This is done using the jsr 303specification implemented by hibernate, which needs to pass in a validator and an entity to be verified, so how does the validator get, 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 into service:
@ Autowired
Private Validator validator
So how is the class BeanValidators implemented? In fact, the implementation is very simple, as long as to judge the annotations of jsr 303 on the ok.
So where are the notes for jsr 303? 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;// province
@ NotNull
Private String city;// city
@ NotNull
Private String county;// area
Whether private Boolean isDefault = false;// is the default address
@ ManyToOne (cascade= {CascadeType.ALL})
@ JoinColumn (name= "uid")
Private User user
}
Write the constraints you need to judge, if reasonable, you can carry out business operations, so as to operate on the database.
Verification of this block is necessary, and one of the main reasons is that such validation can avoid the insertion of dirty data. If readers have the experience of formally launching, they can understand that any code error can be tolerated and modified, but if there is a dirty data problem, it can be a devastating disaster. Problems with the program can be modified, but the presence of dirty data may not be able to recover. So this is why it is important to judge the constraints in service before performing business logic operations.
The judgment here is a business logic judgment, which is screened from a business point of view. in addition, there may be different business conditions in many scenarios. You just need to do it in accordance with the requirements.
The constraints are summarized as follows:
Basic judgment constraint (null value and other basic judgment)
Entity attribute constraints (satisfying jsr 303 and other basic judgments)
Business condition constraints (different business constraints proposed by requirements)
Only when these three points are satisfied can we proceed to the next step.
Ok, basically introduced 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 the appropriate place, so how to reasonably throw an exception?
Is it elegant to throw an exception just to throw a RuntimeException? Of course not. For the exception thrown in service, I think there are roughly two ways to throw it:
Throw RumtimeException exception with status code
Throws a RuntimeException exception of the specified type
Compared with these two exception methods, the first exception means that all my exceptions throw RuntimeException exceptions, but I need a status code, and the caller can query what kind of exception service throws according to the status code.
The second exception is to customize a specified exception error on what kind of exception is thrown in service, and then throw the exception.
Generally speaking, if the system has no other special requirements, it is recommended to use the second way in the development and design. But for example, exceptions like basic judgment can be manipulated entirely using the class library provided to us by guava. Jsr 303 exceptions can also be manipulated using their own encapsulated exception judgment class, because both exceptions belong to the underlying judgment, and there is no need to specify special exceptions for them. However, for the third obligation constraint to judge the exception thrown, it is necessary to throw an exception of the specified type.
For
1throw new RuntimeException ("current user not found!")
Define a specific exception class to judge the exception of this obligation:
Public class NotFindUserException extends RuntimeException {
Public NotFindUserException () {
Super ("this user cannot be found")
}
Public NotFindUserException (String message) {
Super (message)
}
}
Then change this to:
1throw new NotFindUserException ("current user not found!")
Or
1throw 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) {
/ / the following are constraints =
/ / 1. User id cannot be empty, and this user does exist
CheckNotNull (uid)
User user = userDao.findOne (uid)
If (null = = user) {
Throw new NotFindUserException ("current user not found!")
}
/ / 2. The required field of the receiving address cannot be empty.
BeanValidators.validateWithException (validator, address)
/ / 3. If the user does not already have a shipping address, it is set to the default shipping address when this shipping address is created
If (ObjectUtils.isEmpty (user.getAddresses () {
Address.setIsDefault (true)
}
/ / = the following is the normally executed business logic =
Address.setUser (user)
Address result = addressDao.save (address)
Return result
}
This kind of service looks more stable and understandable.
Delete ship to address:
Input parameters:
User id
Receiving address id
Constraints:
User id cannot be empty, and this user does exist
The receiving address cannot be empty, and this receiving address does exist
Determine whether this shipping address is the user's shipping address.
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 the shipping address added above, so I will not repeat it. The service design of delete is as follows: @ Override
Public void deleteAddress (Integer uid, Integer aid) {
/ / the following are constraints =
/ / 1. User id cannot be empty, and this user does exist
CheckNotNull (uid)
User user = userDao.findOne (uid)
If (null = = user) {
Throw new NotFindUserException ()
}
/ / 2. The receiving address cannot be empty, and this receiving address does exist
CheckNotNull (aid)
Address address = addressDao.findOne (aid)
If (null = = address) {
Throw new NotFindAddressException ()
}
/ / 3. Determine whether this shipping address is the user's shipping address.
If (! address.getUser (). Equals (user)) {
Throw new NotMatchUserAddressException ()
}
/ / 4. Determine whether this shipping address is the default shipping address. If it is the default shipping address, it cannot be deleted.
If (address.getIsDefault ()) {
Throw new DefaultAddressNotDeleteException ()
}
/ / = the following is the normally executed business logic =
AddressDao.delete (address)
}
Four related exception classes are designed: NotFindUserException,NotFindAddressException,NotMatchUserAddressException,DefaultAddressNotDeleteException. Different exceptions are thrown according to different business requirements.
Get the list of shipping addresses:
Input parameters:
User id
Constraints:
User id cannot be empty, and this user does exist
The code is as follows: @ Override
Public List listAddresses (Integer uid) {
/ / the following are constraints =
/ / 1. User id cannot be empty, and this user does exist
CheckNotNull (uid)
User user = userDao.findOne (uid)
If (null = = user) {
Throw new NotFindUserException ()
}
/ / = the following is the normally executed business logic =
User result = userDao.findOne (uid)
Return result.getAddresses ()
}
Api abnormal design
There are roughly two ways to throw:
Throw RumtimeException exception with status code
Throws a RuntimeException exception of the specified type
This is mentioned when designing the service layer exception. Through the introduction of the service layer, we choose the second way to throw the exception when the service layer throws the exception. The difference is that we need to use these two ways to throw the exception in the api layer: to specify the type of the api exception and to specify the relevant status code before throwing the exception. The core of this exception design is to enable the user calling api to understand the details of the exception more clearly. In addition to throwing an exception, we also need to make a corresponding table to show the details of the exception corresponding to the status code and the possible problems of the exception to the user, so as to facilitate the user's query. (such as api documents provided by github, api documents provided by Wechat, etc.), there is another advantage: if users need custom prompt messages, they can modify the prompts according to the returned status code.
Api validation constraint
First of all, for the design of api, there needs to be a dto object, which is responsible for communicating and passing data with the caller, and then dto- > domain is passed to service for operation, which must be noted. Second, in addition to the basic judgment (null judgment) and jsr 303 verification, the api layer also needs relevant verification. If the verification fails, it will be returned directly to the caller. If you are told that the call failed and that you should not access service with illegal data, the reader may be confused about why the api layer still needs authentication if service has already verified it. Here we design a concept: Murphy's law in programming, if the data verification of the api layer is negligent, then the illegal data may be brought to the service layer, and then the dirty data is saved to the database.
So the core of careful programming is: never believe that the data you receive is legal.
Api abnormal design
When designing api layer exceptions, as we said above, you need to provide error codes and error messages, so you can design it to provide a generic api superclass exception from which other different api exceptions inherit:
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 the api layer exception: ApiDefaultAddressNotDeleteException,ApiNotFindAddressException,ApiNotFindUserException,ApiNotMatchUserAddressException.
For example, the default address cannot be deleted:
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 codes are as follows:
Public abstract class AddressErrorCode {
Public static final Long DefaultAddressNotDeleteErrorCode = 10001L / the default address cannot be deleted
Public static final Long NotFindAddressErrorCode = 10002Lbomacht / this shipping address cannot be found
Public static final Long NotFindUserErrorCode = 10003Lumbago / this user could not be found
Public static final Long NotMatchUserAddressErrorCode = 10004Lumbram / user does not match the shipping address
}
Ok, then the exception of the api layer has been designed. Here, the AddressErrorCode error code class stores the error codes that may occur. It is more reasonable to put it in the configuration file to manage it.
Api handles exceptions
The api layer will call the service layer, and then handle all the exceptions in the service. First of all, you need to make sure that the api layer is very light, basically making a forwarding function (interface parameters, passing to the service parameter, returning data to the caller, these three basic functions), and then handling the exception on the method call passed to the service parameter.
Here is only an example of adding an address:
@ Autowired
Private IAddressService addressService
/ * *
* add a 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 ("user not 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 determine the type of exception when calling service, and then convert any service exception into an api exception, and then throw an api exception, which is a commonly used method of exception conversion. Similarly, deleting the receiving address and obtaining the receiving address are also handled in the same way, and I will not repeat them here.
Api abnormal conversion
I've explained how to throw an exception and how to convert a service exception to an api exception, so does throwing an api exception directly complete the exception handling? The answer is no. When an api exception is thrown, we need to make the data (json or xml) returned by the api exception clear to the user. 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 exception (Exception exception,HttpServletResponse response) {
ErrorDTO errorDTO = new ErrorDTO ()
If (exception instanceof ApiException) {/ / api exception
ApiException apiException = (ApiException) exception
ErrorDTO.setErrorCode (apiException.getErrorCode ())
} else {/ / unknown exception
ErrorDTO.setErrorCode (0L)
}
ErrorDTO.setTip (exception.getMessage ())
ResponseEntity responseEntity = new ResponseEntity (errorDTO,HttpStatus.valueOf (response.getStatus ()
Return responseEntity
}
@ Setter
@ Getter
Class ErrorDTO {
Private Long errorCode
Private String tip
}
}
Ok, which completes the conversion of api exceptions into user-readable DTO objects. @ ControllerAdvice is used in the code, which is a special aspect handling provided by spring MVC.
When an exception occurs when calling the api API, the user can also receive the normal data format. For example, when there is no user (uid is 2), the shipping address is added for that user, and the data after postman (Google plugin is used to simulate the http request):
{
"errorCode": 10003
"tip": "the user cannot be found"
}
This is the end of the article on "how to elegantly design Java exceptions". Thank you for reading! I believe you all have a certain understanding of "how to elegantly design Java anomalies". If you want to learn more, you are welcome to follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.