In our daily project development process, exceptions are generally the most headache for programmers. The throwing, capturing and processing of exceptions not only involve transaction rollback, but also involve the return of front-end message reminder information. So how can we design to solve the above two pain points? Can we unify the business logic and give the front-end corresponding exception alert content?
Objective in this chapter
Based on SpringBoot platform to build unified processing of business logic exceptions, abnormal message content formatting.
Benefits to the
Tencent cloud preferential server 10 yuan/month, click to join the group
SpringBoot Enterprise core technology learning project
project | Project name | Project description |
---|---|---|
001 | Spring Boot core technology | Explain some of the core components of SpringBoot at the enterprise level |
002 | Spring Boot core technology chapter source code | Spring Boot core technology book each article code cloud corresponding source code |
003 | Spring Cloud core technology | Full presentation of Spring Cloud core technologies |
004 | Spring Cloud core technology chapter source code | Spring Cloud core technology brief book each article corresponding source code |
005 | QueryDSL core technology | Full introduction to QueryDSL core technology and SpringDataJPA integration based on SpringBoot |
006 | SpringDataJPA core technology | Full presentation of SpringDataJPA core technology |
007 | SpringBoot Core technology learning directory | SpringBoot system learning directory, please focus on praise!! ! |
Build the project
We extracted the logical exception core handling part as a separate JAR for other modules to reference, and created the project to add a dependency for common use in the parent project pom.xml. The configuration is as follows:
<dependencies> <! --Lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <! > <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <! -- Web dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>Copy the code
After the project is created, except. Idea, IML, and POM. XML, all other items are deleted.
Exception handling core submodule
We create a submodule named springboot-core-exception, in which we define a custom LogicException RuntimeException class, inherit RuntimeException and rewrite the constructor as follows:
/ custom business logic exception class * * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2018/1/7 * Time: 2:38 PM * Code cloud: http://git.oschina.net/jnyqy * ======================== * * @author yuqiyu */ public class LogicException extends RuntimeException {/** * Log object */ private Logger Logger = LoggerFactory.getLogger(LogicException); /** * error message content */ protected String errMsg; /** * error code */ protected String errCode; /** * list of parameters required to format error codes */ protected String[] params; /** * Get error message content * get unformatted error message content * from redis according to errCode and format the error message with the argument ** @ using the string.format () methodreturn
*/
public String getErrMsg() {
returnerrMsg; } /** * get error code ** @return
*/
public String getErrCode() {
returnerrCode; } /** * get the list of exception parameters ** @return
*/
public String[] getParams() {
returnparams; } /** * public LogicException(String errCode, String errCode, String param params) String... params) { this.errCode = errCode; this.params = params; / / get the exception message content this. After formatting errMsg. = ErrorMessageTools getErrorMessage (errCode, params); // Error message logger. Error ("The system encounters the following exception: {}>>> Exception information: {}", errCode, errMsg); }}Copy the code
ErrCode and params are passed in the overridden constructor to initialize global variables in the class.
errCode
: This field is the corresponding exception code, we will create an enumeration to store the exception error code in the content of the subsequent article, anderrCode
Is the value of the string corresponding to the enumeration.params
: Here is the correspondenceerrCode
String Meaning Indicates the list of parameters required for the description.errMsg
: a formatted description of the business logic exception message that we can see called in the constructorErrorMessageTools.getErrorMessage(errCode,params);
The function of this method is to obtain the unformatted exception description from the database through the exception code, and format the exception message description by passing the parameter.
The purpose of creating the exception core package is to allow other modules to add dependencies directly. How do you get the exception description content?
Define the exception message fetch interface
We add an interface LogicExceptionMessage to the Springboot-exception-core module, which provides a method to obtain unformatted exception message description content through exception codes. The interface definition is as follows:
/** * logical exception interface definition * Use the project to implement the interface method and provide method implementation * errCode corresponding logical exception code * getMessage returns a string for the logical exception message content * ======================== * Created * Date: 2018/1/7 * Time: 2:41 PM * 码云 : http://git.oschina.net/jnyqy * ======================== * @author yuqiyu */ public interface LogicExceptionMessage { /** * Obtain the content of the abnormal message * @param errCode Error code * @return
*/
public String getMessage(String errCode);
}
Copy the code
In projects that need to load springboot-exception-core dependencies, By creating the entity class to implement the LogicExceptionMessage interface and overwriting the getMessage(String errCode) method, we can obtain the data through spring IOC to obtain the implementation class instance, which will be involved in writing the exception module below.
Format the exception message utility class
The constructor ErrorMessageTools tool class provides a getErrorMessage method to get a formatted description of an exception message. The code is as follows:
/ * * * the exception message describes formatting utility class * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2018/1/7 * Time: 2:40 PM * Code cloud: http://git.oschina.net/jnyqy * ======================== * * @author yuqiyu */ public class ErrorMessageTools { /** * Obtain exception message * * @param errCode Exception message code * @param params List of parameters required to format exception parameters * @return*/ public static String getErrorMessage(String errCode, Object... Params) {// Get business logic message to implement LogicExceptionMessage LogicExceptionMessage = SpringBeanTools.getBean(LogicExceptionMessage.class);if (ObjectUtils.isEmpty(logicExceptionMessage)) {
try {
throw new Exception("Configure to implement the LogicExceptionMessage interface and set the implementation class to be managed by SpringIoc."); } catch (Exception e) { e.printStackTrace(); }} / / get the error message content String errMsg = logicExceptionMessage. GetMessage (errCode); // Format error message contentreturnObjectUtils.isEmpty(params) ? errMsg : String.format(errMsg, params); }}Copy the code
Note: Because our utility classes are static method invocations, we cannot directly get the LogicExceptionMessage instance using Spring IOC annotation injection.
Because we cannot inject an instance, in the getErrorMessage method we use the SpringBeanTools tool class to get an instance of the ApplicationContext and then use the context to get a Bean of the specified type. Call the getMessage method after getting the LogicExceptionMessage instance. Based on the passed errCode, you can get the unformatted exception description directly from the interface implementation class instance!
Of course, the implementation class can be Redis, Map collection, database, text as the data source.
Format method and passed parameters can be used to obtain the formatted String. For example:
Unformatted exception message => User: %s has been frozen and cannot be operated. Format code => string.format ("%s has been frozen and cannot operate."."Heng Yu Youth"); Effect after formatting => User: Hengyu teenager has been frozen, unable to operate.Copy the code
For details about formatting special characters, see the String.format documentation. For details about how to obtain ApplicationContext objects, see Chapter 32: How to Obtain ApplicationContext Objects for the SpringBoot Project.
Back inside the LogicException constructor, the value of the errMsg field will be the formatted description of the exception message, which can be obtained externally by calling the getErrMsg method.
Now that we’ve coded the Springboot-exception-core module, let’s look at how to use our custom business logic exception and get a formatted description of the exception message.
Exception Example module
Springboot-exception-example = springboot-exception-core = springboot-exception-core = springboot-exception-core = springboot-exception-core = springboot-exception-core = springboot-exception-core = springboot-exception-core The pom. XML configuration file content is as follows:
<dependencies> <! --> <dependency> <groupId>com.hengyu</groupId> <artifactId>springboot-exception-core</artifactId> The < version > 0.0.1 - the SNAPSHOT < / version > < / dependency > <! <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <! <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector- Java </artifactId> <scope>runtime</scope> </dependency> <! -- Druid dependency --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> The < version > 1.1.6 < / version > < / dependency > < / dependencies >Copy the code
Let’s configure the configuration required for our sample project application.yml file, as follows:
spring:
application:
name: springboot-exception-core
# data source configuration
datasource:
druid:
url: jdbc:mysql://localhost:3306/test? useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
jpa:
properties:
hibernate:
Configure to display SQL
show_sql: true
Configure the formatted SQL
format_sql: true
Copy the code
Above we have talked about the LogicExceptionMessage to get the content can be read from a variety of data sources, we still use the database to read, recommend the formal environment in redis cache!!
Exception information table
Create exception information table sys_EXCEPtion_info in database as follows:
DROP TABLE IF EXISTS `sys_exception_info`; / *! 40101 SET @saved_cs_client = @@character_set_client */; / *! 40101 SET character_set_client = utf8 */; CREATE TABLE `sys_exception_info` ( `EI_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT'Primary key increment',
`EI_CODE` varchar(30) DEFAULT NULL COMMENT 'Exception code',
`EI_MESSAGE` varchar(50) DEFAULT NULL COMMENT 'Exception message content',
PRIMARY KEY (`EI_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='Basic System Exception Information'; / *! 40101 SET character_set_client = @saved_cs_client */; -- -- Dumping datafortable `sys_exception_info` -- LOCK TABLES `sys_exception_info` WRITE; / *! 40000 ALTER TABLE `sys_exception_info` DISABLE KEYS */; INSERT INTO `sys_exception_info` VALUES (1,'USER_NOT_FOUND'.'User does not exist.'), (2,'USER_STATUS_FAILD'.'User status is abnormal.'); / *! 40000 ALTER TABLE `sys_exception_info` ENABLE KEYS */; UNLOCK TABLES;Copy the code
We use Spring-data-jPA to achieve data reading, and create the Entity corresponding to the data table below.
Exception information entity
/ * * * system exception basic information entity * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2018/1/7 * Time: Afternoon cause * yards cloud: http://git.oschina.net/jnyqy * = = = = = = = = = = = = = = = = = = = = = = = = * @ author yuqiyu * / @ Data @ Entity @ Table (name ="sys_exception_info") public Class ExceptionInfoEntity implements Serializable{/** * Exception Message Number */ @id@generatedValue @column (name ="EI_ID") private Integer id; /** * error message */ @column (name ="EI_CODE") private String code; /** * error message content */ @column (name ="EI_MESSAGE")
private String message;
}
Copy the code
Exception information data interface
/ abnormal data interface definition * * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2018/1/7 * Time: afternoon 3:34 surely * yards cloud: http://git.oschina.net/jnyqy * ======================== * @author yuqiyu */ public interface ExceptionRepository extends JpaRepository < ExceptionInfoEntity, Integer > {/ * * * according to the code for anomaly configuration information * @ param code exception code * @return
*/
ExceptionInfoEntity findTopByCode(String code);
}
Copy the code
In the data interface, the spring-data-jPA method is used to query and errCode is used to read the entity content of exception information.
The errCode that is used when an exception runs out during development is stored in the enumeration type or constant interface. Here we choose the relatively strong extensible enumeration type, and the code is as follows:
/ * * * * an enum type to error code = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: Time: 2018/1/7 * * yards cloud: good afternoon http://git.oschina.net/jnyqy * = = = = = = = = = = = = = = = = = = = = = = = = * @ author yuqiyu * / public enum ErrorCodeEnum {/ user does not exist. * * * * / USER_NOT_FOUND, abnormal/user state * * *. * / USER_STATUS_FAILD, / /... Add additional error codes}Copy the code
Exception code enumeration items need to be changed according to the database exception information table, to ensure that when we throw exceptions, there is the corresponding information in the database.
LogicExceptionMessage implements the class definition
We added the LogicExceptionMessage interface definition in the springboot-exception-core core module. We need to implement the getMessage method core module of the interface, so as to obtain the corresponding exception information in the database. The implementation class is shown as follows:
/ * * * business logic exception message for implementation class * - messages can get * - from the database can be obtained from inside Redis * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: Date: 2018/1/7 * Time: 3:16 PM * code cloud: http://git.oschina.net/jnyqy * ======================== * @author yuqiyu */ @Component public class Abnormal LogicExceptionMessageSupport implements LogicExceptionMessage {/ * * * * / data interface @autowired private ExceptionRepository exceptionRepository; /** * Obtain error information based on the error code * @param errCode Error code * @return
*/
@Override
public String getMessage(String errCode) {
ExceptionInfoEntity exceptionInfoEntity = exceptionRepository.findTopByCode(errCode);
if(! ObjectUtils.isEmpty(exceptionInfoEntity)) {return exceptionInfoEntity.getMessage();
}
return "System exception"; }}Copy the code
In the getMessage method, the findTopByCode method defined by the ExceptionRepository data interface gets the exception information of the specified exception. If there is an exception information, an unformatted exception description is returned.
Uniform return entity definition
For interface (including both before and after the separation projects) in the treatment of the return to the unified format, we usually adopt the way of fixed entity, so for the front end of the call interface developers parse the content is more convenient, also in the development process of meeting convention system, an abnormal business logic returns the format of the content, Of course, this is the same format as requesting the correct return of the interface, but the field content is different. ApiResponseEntity
returns the following:
/ * * * * interface response entity = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2018/1/9 * Time: at 3:04 p.m * code: http://git.oschina.net/jnyqy * ======================== * @author yuqiyu */ @Data @Builder public class ApiResponseEntity<T extends Object> {/** * private String errorMsg; /** * private T data; }Copy the code
In the ApiResponseEntity entity, Lombok’s constructor design pattern @Builder annotation is adopted, and the entity configured with this annotation automatically adds the internal class implementation design pattern to the.class file, partially generating the following code:
// ...
public static class ApiResponseEntityBuilder<T> {
private String errorMsg;
private T data;
ApiResponseEntityBuilder() {
}
public ApiResponseEntity.ApiResponseEntityBuilder<T> errorMsg(String errorMsg) {
this.errorMsg = errorMsg;
return this;
}
public ApiResponseEntity.ApiResponseEntityBuilder<T> data(T data) {
this.data = data;
return this;
}
public ApiResponseEntity<T> build() {
return new ApiResponseEntity(this.errorMsg, this.data);
}
public String toString() {
return "ApiResponseEntity.ApiResponseEntityBuilder(errorMsg=" + this.errorMsg + ", data=" + this.data + ")"; }} / /...Copy the code
So far, we haven’t added any global exception configuration. For global exception configuration, we use @ControllerAdvice as described in the previous section. For @ControllerAdvice, see Chapter 21: Global Exception Handling in the SpringBoot Project.
Global exception notification definition
We only add the handling of business logic exceptions in this chapter, and the specific coding is as follows:
/ controller exception notification class * * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2018/1/7 * Time: at 5:30 PM * yards cloud: http://git.oschina.net/jnyqy * ======================== * * @author yuqiyu */ @ControllerAdvice(annotations = RestController.class) @ResponseBody public class ExceptionAdvice { /** * logback new instance */ Logger logger = LoggerFactory.getLogger(this.getClass()); /** * handle business logic exception ** @param e Business logic exception object instance * @return*/ @ExceptionHandler(logicException.class) @responseStatus (code = httpstatus.ok) public ApiResponseEntity<String> logicException(LogicException e) { logger.error("Business logic exception encountered: [{}]", e.getErrCode()); // Return the content of the response entityreturnApiResponseEntity.<String>builder().errorMsg(e.getErrMsg()).build(); }}Copy the code
I was recently asked in the tech group, “Why do we need to configure @responseBody here if we’re using @restController?” Just to give you an explanation, our controller notifications really listen to @RestController, and the @RestController annotated controller consistently returns JSON data. So when we get an exception, the request is no longer in the controller, it’s been delivered to the controller notification class, so if the notification class also wants to return JSON data, we need to configure the @responseBody annotation to do that.
Let’s look at the logicException() method above, whose return value is a uniform return entity that we define to return the same format as the correct request when a business logicException is encountered.
@ ExceptionHandler
Configured to be processedLogicException
Type of exception, that is, whenever encountered by the systemLogicException
This method is called when an exception is thrown to the controller.@ResponseStatus
The returned status value was configured because we encountered a business logic exception and the front end definitely needed not a 500 error, but a 200 stateJSON
Description of service exceptions.
The assignment of the field errorMsg is achieved by using the constructor design pattern when the method returns and passing the exception message to the errorMsg() method.
test
With the exception code completed, let’s create a test controller to simulate how the system returns when the business logic occurs. The test control content is as follows:
/ * * * * test controller = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2018/1/7 * Time: in the afternoon that day * code: http://git.oschina.net/jnyqy * ======================== * * @author yuqiyu */ @RestController public class IndexController {/** **return
*/
@RequestMapping(value = "/index") public ApiResponseEntity<String> index() throws LogicException {/** * simulates that the user does not exist. Throws a service LogicExceptionif (true) {
throw new LogicException(ErrorCodeEnum.USER_STATUS_FAILD.toString());
}
return ApiResponseEntity.<String>builder().data("this is index mapping").build(); }}Copy the code
According to the above code, USER_STATUS_FAILD service logic exception will occur when we access /index. According to our previous global exception configuration and unified entity instantiation, JSON data in ApiResponseEntity format will appear after access. Let’s run project access to see the effect. The output is as follows:
{
"errorMsg": "User status is abnormal."."data": null
}
Copy the code
On the console, since we wrote the log message, we also have the corresponding output, as follows:
Hibernate:
select
exceptioni0_.ei_id as ei_id1_0_,
exceptioni0_.ei_code as ei_code2_0_,
exceptioni0_.ei_message as ei_messa3_0_
from
sys_exception_info exceptioni0_
where
exceptioni0_.ei_code=? limit? The 2018-01-09 18:54:00. ERROR 647-2024 [nio - 8080 - exec - 1] C.H.S.E xception. Core. LogicException: The system encounters the following exception: USER_STATUS_FAILD>>> Exception information: Abnormal state of the user. The 2018-01-09 18:54:00. 649 ERROR 2024 - [nio - 8080 - exec - 1] C.H.S.E.C.A dvice. ExceptionAdvice: Service logic exception: USER_STATUS_FAILDCopy the code
If the business LogicException is in the Service layer, we don’t need to worry about transaction rollback at all, because logicexceptions themselves are runtime exceptions, and transactions are automatically rolled back when a runtime exception is thrown in the project.
We will mask the business logic exception and change true to false to see the correct return format, as follows:
{
"errorMsg": null,
"data": "this is index mapping"
}
Copy the code
If you want to change the corresponding NULL to an empty string, see Chapter 5: Configuring To Return Json Views with FastJson.
conclusion
This chapter integrates some contents of previous chapters, including global exception, unified format return, etc. This method is currently being used in our company’s products, which can meet the usual business logic exception definition and return. We can store the exception message in the database and update the prompt content at any time, which is relatively easy to use.
This chapter source code has been uploaded to the code cloud: SpringBoot matching source address: gitee.com/hengboy/spr… SpringCloud source code address: gitee.com/hengboy/spr… SpringBoot can be found in: directory: SpringBoot Learning directory: QueryDSL General Query Framework Learning directory: SpringDataJPA SpringDataJPA Learning directory, thanks for reading!