A preface.

Hello, everyone, and have a nice weekend. What did everyone do on the weekend? The MSI? The speed of 9? Brush play? Going out drinking and dancing? Picnic spring outing… All right~ the holiday always passes quickly, the weekend brushes breaking Bad at home, worthy is every season douban frequency cent 9+ god drama, the whole process does not have the urine point, recommends everybody to watch.

Back to the point, play to play, play to play, don’t make fun of bugs. It’s normal for programmers to leave bugs behind in the process of writing code. When a bug occurs in the program, there will always be a corresponding log message, and the stack error thrown by the back end cannot be directly thrown to the front end. Imagine that when a user searches for a nonexistent item, the backend code has a bug (normal business code will check the existence of the item) and reports a null pointer exception. This is to directly return the stack information of the null pointer exception to the user without any error packaging. Oh, good. The boss can’t afford not to buy you a cup of tea.

So how do we deal with these exceptions? In this article, I will show you how to gracefully customize global exceptions in Spring. If this is not correct, or if you think there is a better way to do it, please comment.

Abnormal.

To talk about global exception handling, let’s first understand the exception architecture in Java.

instructions

1.Throwable

All exceptions are direct or indirect subclasses of Throwable. Throwable has two direct subclasses, Error and Exception.

2.Error

Error is an Error, and is thrown for all compile-time errors and system errors. These errors indicate that the fault occurs on the VM itself or when the VM tries to execute an application, such as a Java Virtual Machine running error or a NoClassDefFoundError. These errors are not detectable because they are outside the control and processing capabilities of the application, and most of them are not allowed to occur while the program is running. Even if an error does occur, a properly designed application should not, by nature, attempt to handle the exception it raises. In Java, errors are described by subclasses of Error.

3.Exception

It specifies exceptions that the program itself can handle. The difference between exceptions and errors is that exceptions can be handled and errors cannot be handled.

4.Checked out

Checkable exceptions, which are very common when coding, all checked exceptions need to be handled in your code. Their occurrence is predictable, normal, and reasonably manageable. For example, IOException.

4.Unchecked, Unchecked, Unchecked, Unchecked, Unchecked

RuntimeException and its subclasses are both unchecked Exceptions. Examples include NPE null-pointer exceptions, ArithmeticException with zero divisor, and so on. Exceptions occur at runtime and cannot be caught and handled in advance. Error is also an unchecked exception and cannot be handled in advance.

Three. The way of exception handling

1.try-catch-finally

This approach is the most common in a single business method, where business logic predictions within a try block may raise an exception.

For example, reading a file forces you to handle IOException checked

@param fileName @return */ public String readFileContent(String fileName) {File File = new File(fileName); BufferedReader reader = null; StringBuffer sbf = new StringBuffer(); try { reader = new BufferedReader(new FileReader(file)); String tempStr; while ((tempStr = reader.readLine()) ! = null) { sbf.append(tempStr).append("\n"); } reader.close(); return sbf.toString(); } catch (IOException e) { e.printStackTrace(); } finally { if (reader ! = null) { try { reader.close(); } catch (IOException e1) { e1.printStackTrace(); } } //doSomething } return sbf.toString(); }Copy the code

2.try-with-resource-finally

Try-with-resources is a new exception handling mechanism in JDK 7 that makes it easy (and elegant) to close resources used in try-catch blocks. In the first process, the stream is also closed manually in finally. Using try-with-resource-finally can save you this step of code.

@param fileName @return */ public String readFileContent(String fileName) {File File = new File(fileName); StringBuffer sbf = new StringBuffer(); try ( BufferedReader reader = new BufferedReader(new FileReader(file))){ String tempStr; while ((tempStr = reader.readLine()) ! = null) { sbf.append(tempStr).append("\n"); } reader.close(); return sbf.toString(); } catch (IOException e) { e.printStackTrace(); } finally { //doSomething } return sbf.toString(); }Copy the code

3. Handle global exceptions

The above two methods handle predictable exceptions within a method. What if an unexpected exception occurs? Some friends will say, I directly use catch(Exception ex) package to deal with exceptions. However, if in the micro-service, the order center calls the payment center, and the payment center is abnormal, the payment center itself catches the abnormal, and the order center thinks that the payment is successful and places the order successfully, which is cool…

So the payment center has to throw an exception, tell the order center, I’ve got an exception. The order center received an exception and terminated processing. Terminating always gives the front end an error code. How to define this error code? Try-catch? So this is still just an order placing scenario. What if I had to specify a separate error code for each business scenario, and I had to define a try-catch block for each method? Obviously that’s not possible, not to mention the fact that a lot of try-catch blocks are going to affect the efficiency of your program, and you’re going to have to write how many exceptions to handle and I’m guessing you’re going to get bored. This is where global exception handling comes in. For a specific service exception, a code code is defined and returned to the global exception processing. The global processor parses the code mapping service exception and returns the standard output to the front-end for display.

Handle global exceptions in Spring

4.1. @ ExceptionHandler

A single type of exception is handled uniformly, thereby reducing code duplication and complexity

1. The abnormal request is not processed

@RestController public class TestController { @RequestMapping("/test") public Object test(){ / / throw the Java. Lang. ArithmeticException: / by zero abnormal int I = 1/0. return new Date(); }}Copy the code

show

2. Handle abnormal requests

public class TestController { @RequestMapping("/test") public Object test(){ int i = 1 / 0; return new Date(); } @ExceptionHandler({RuntimeException.class}) public Object catchException(Exception ex){ return ex.getMessage(); }}Copy the code

show

4.2. HandlerExceptionResolver

Centralized exception processing: Handle exceptions through interfaces

1. The abnormal request is not processed

In accordance with 4.1

2. Handle abnormal requests

@Component
public class GlobalExceptionHandler implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelMap mmp=new ModelMap();
        mmp.addAttribute("ex",ex.getMessage());
        response.addHeader("Content-Type","application/json;charset=UTF-8");
        try {
            new ObjectMapper().writeValue(response.getWriter(),ex.getMessage());
            response.getWriter().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }

}
Copy the code

4.3@ControllerAdvice is combined with @ExceptionHandler

Exception centralized handling: annotation form

1. The abnormal request is not processed

In accordance with 4.1

2. Handle abnormal requests

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class) public Object handle(RuntimeException ex) { return ex.getMessage(); }}Copy the code

Studious friend might want to know the principle of the above three ways, as a source code parsing link: www.cnblogs.com/lvbinbin2yu…

5. Graceful exception return

5.1. Unified data return format

Ok, now that we know the types of exceptions and the unified way to catch exceptions, how do we agree on the return of data to our front end colleagues? You can’t have every interface on the back end telling the front end that one interface returns an abnormal message string and the other interface returns a List of normal data. Well, I guess the front brother’s gonna have to kick your ass

Then it is important to define a unified return entity, without further ado into the code

@data public class BaseResult {/** * httpCode */ private Integer code; /** * business code */ private String errorCode; /** * private String message; / * * * link id 【 micro service request calls link tracking, did not understand this concept, can have a look at my another blog: https://juejin.cn/post/6923004276335869960] * / private String traceId; public BaseResult() { } public BaseResult(Integer code, String message) { this.code = code; this.message = message; } public BaseResult(Integer code, String errorCode, String message) { this.code = code; this.errorCode = errorCode; this.message = message; } /** * public static final Integer CODE_SUCCESS = 200; public static final Integer CODE_SYSTEM_ERROR = 500; Public static final String SYSTEM_ERROR = "SYSTEM_ERROR "; Public static final String MESSAGE_SUCCESS = "Request success "; Public static final String QUERY_SUCCESS = "query success "; Public static final String INSERT_SUCCESS = "insert success "; Public static final String UPDATE_SUCCESS = "UPDATE_SUCCESS "; Public static final String DELETE_SUCCESS = "delete success "; Public static final String IMPORT_SUCCESS = "Import successful "; Public static final String EXPORT_SUCCESS = "Export successful "; Public static final String DOWNLOAD_SUCCESS = "Download successful "; }Copy the code
@data @equalSandHashCode (callSuper = true) public class Result<T> extends BaseResult { public Result() { } public Result(Integer code, String message, T data) { super(code, message); this.data = data; } public Result(Integer code, String errorCode, String message, T data) { super(code, errorCode, message); this.data = data; } public boolean success() { return CODE_SUCCESS.equals(getCode()); } public boolean systemFail() { return CODE_SYSTEM_ERROR.equals(getCode()); } public static Result<Object> ok() { return new Result<>(CODE_SUCCESS, "", null); } public static Result<Object> ok(String message) { return new Result<>(CODE_SUCCESS, message, null); } public static <T> Result<T> success(T data) { return new Result<>(CODE_SUCCESS, MESSAGE_SUCCESS, data); } public static <T> Result<T> success(T data, String message) { return new Result<>(CODE_SUCCESS, message, data); } public static Result<Object> error(String message) { return Result.error(CODE_SYSTEM_ERROR, null, message, null); } public static Result<Object> error(String errorCode, String message) { return Result.error(CODE_SYSTEM_ERROR, errorCode, message, null); } public static Result<Object> error(Integer code, String errorCode, String message, Object data) { return new Result<>(code, errorCode, message, data); }}Copy the code
// If it is a list page, it must return the total number of entries to the front end, @data @equalSandHashCode (callSuper = true) public class PageResult<T> extends BaseResult {private Long total; private List<T> data; public PageResult() { } public static <T> PageResult<T> ok(Page<T> result) { PageResult<T> pageResult = new PageResult<>(); pageResult.setCode(CODE_SUCCESS); pageResult.setMessage(QUERY_SUCCESS); pageResult.setTotal(result.getTotal()); pageResult.setData(result.getRecords()); return pageResult; }}Copy the code

5.2. Exception handling

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class) public Object handle(RuntimeException ex) { return Result.error(errorCode.toString(), errorMessage.toString()); }}Copy the code

Now let’s take a look, again using the demo code above, at what the exception return looks like.

5.3. Abnormal standardization treatment

See this small friends may feel that the exception handling is still nothing, but still throw out the code inside the exception, the front end will directly display the message inside the message as the end of the user operation prompt. You gave an error and returned a /by zero. The user knows what happened to his operation. So here we also need to have different business exception prompt mapping mechanisms for different exceptions.

With mapping rules for global business exception handling, what should we use? Does it match my exception and return my custom business prompt?

Internationalization function ah!!

About the internationalization functions, friend if you have don’t understand, you can refer to this article: blog.csdn.net/u012234419/…

I define the code in the internationalization configuration file, the business exception throws the corresponding code, the global exception mapping is not good.

Ok, the above code [here for the convenience of demonstration, only provide the corresponding Chinese version of internationalization code]

5.3.1. Messages defined. The properties

Write content

Id.is. null= The user ID cannot be emptyCopy the code

5.3.2. Define internationalization configuration classes

@Component public class SpringMessageSourceErrorMessageSource { @Autowired private MessageSource messageSource; @Override public String getMessage(String code, Object... params) { return messageSource.getMessage(code, params, LocaleContextHolder.getLocale()); } @Override public String getMessage(String code, String defaultMessage, Object... params) { return messageSource.getMessage(code, params, defaultMessage, LocaleContextHolder.getLocale()); }}Copy the code

5.3.3. Unified services are abnormal

All service exceptions in the system inherit this exception. Global exceptions can handle service exceptions by capturing this exception

@data @EqualSandHashCode (callSuper = true) Public Class ServiceException extends RuntimeException {private static final long serialVersionUID = 430933593095358673L; private String errorMessage; private String errorCode; /** * construct a new instance. */ public ServiceException() { super(); } /** * constructs a new instance with the given exception information. * @param errorMessage Indicates an exception. */ public ServiceException(String errorMessage) { super((String)null); this.errorMessage = errorMessage; } // omit some code}Copy the code
@equalSandHashCode (callSuper = true) Public Class ValidationException extends ServiceException {@getter private Object[] params; public ValidationException(String message) { super(message); } public ValidationException(String message, Object[] params) { super(message); this.params = params; } public ValidationException(String code, String message, Object[] params) { super(code, message); this.params = params; } public static ValidationException of(String code, Object[] params) { return new ValidationException(code, null, params); }}Copy the code

5.3.4. Define subclass exception verification tool classes

public class ValidationUtil { public static void isTrue(boolean expect, String code, Object... params) { if (! expect) { throw ValidationException.of(code, params); } } public static void isFalse(boolean expect, String code, Object... params) { isTrue(! expect, code, params); } // omit some code}Copy the code

5.3.5. Define global exception handling

@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @Autowired private SpringMessageSourceErrorMessageSource messageSource; @ExceptionHandler(ConstraintViolationException.class) public Object handle(ConstraintViolationException ex) { StringBuilder errorCode = new StringBuilder(); StringBuilder errorMessage = new StringBuilder(); ex.getConstraintViolations() .stream() .forEach(error -> { if (StrUtil.isNotBlank(errorCode.toString())) { errorCode.append(","); } errorCode.append(error.getMessageTemplate()); if (StrUtil.isNotBlank(errorMessage.toString())) { errorMessage.append(","); } errorMessage.append(error.getMessage()); }); return Result.error(errorCode.toString(), errorMessage.toString()); } @ExceptionHandler(MethodArgumentNotValidException.class) public Object handle(MethodArgumentNotValidException ex) { StringBuilder errorCode = new StringBuilder(); StringBuilder errorMessage = new StringBuilder(); buildBindingResult(errorCode, errorMessage, ex.getBindingResult()); return Result.error(errorCode.toString(), errorMessage.toString()); } @ExceptionHandler(ValidationException.class) public Object handle(ValidationException ex) { String errorMessage = messageSource.getMessage(ex.getErrorCode(), ex.getMessage(), ex.getParams()); return Result.error(ex.getErrorCode(), errorMessage); } @ExceptionHandler(ServiceException.class) public Object handle(ServiceException ex) { return Result.error(ex.getErrorCode(), ex.getMessage()); } @ExceptionHandler(Throwable. Class) public Object Handle (Throwable ex) {log.error(" global exception ", ex); return Result.error(BaseResult.SYSTEM_ERROR); @param messageTemplate * @return */ private String getFromMessageTemplate(String messageTemplate)  { if(StrUtil.isBlank(messageTemplate)){ return null; } if (messageTemplate.length() < 2) { return null; } return messageTemplate.substring(1, messageTemplate.length() - 1); } /** * build and bind return result * @param errorCode errorCode * @param errorMessage internationalization errorMessage * @param bindingResult errorMessage to be handled */ private void buildBindingResult(StringBuilder errorCode, StringBuilder errorMessage, BindingResult bindingResult) { List<ObjectError> errors = bindingResult.getAllErrors(); errors .stream() .forEach(error -> { if (error.contains(ConstraintViolation.class)) { ConstraintViolation constraintViolation = error.unwrap(ConstraintViolation.class); if (errorCode.length() > 0) { errorCode.append(","); } errorCode.append(getFromMessageTemplate(constraintViolation.getMessageTemplate())); } if (errorMessage.length() > 0) { errorMessage.append(","); } String errorInfo = messageSource.getMessage(getFromMessageTemplate(error.getDefaultMessage()), null, (Object) null); errorMessage.append(errorInfo); }); }}Copy the code

5.4. Demonstrate

The demo project structure is as follows

├ ─ ─ demo. On iml ├ ─ ─ pom. The XML └ ─ ─ the SRC └ ─ ─ the main ├ ─ ─ Java │ └ ─ ─ com │ └ ─ ─ examp │ ├ ─ ─ DemoApplication. Java │ ├ ─ ─ the config │ │ └ ─ ─ SpringMessageSourceErrorMessageSource. Java │ ├ ─ ─ controller │ │ └ ─ ─ TestController. Java │ ├ ─ ─ exception │ │ ├ ─ ─ ServiceException. Java │ │ └ ─ ─ is thrown. Java │ ├ ─ ─ handler │ │ └ ─ ─ GlobalExceptionHandler. Java │ ├ ─ ─ model │ │ ├ ─ ─ BaseResult. Java │ │ ├ ─ ─ Result. Java │ │ └ ─ ─ User. Java │ └ ─ ─ util │ └ ─ ─ ValidationUtil. Java └ ─ ─ resources ├ ─ ─ ├─ ├─ messages.propertiesCopy the code

Demonstrate the effect of exception handling

1. In the messages.properties configuration file

Id.is.null = The user ID cannot be empty ID.is.can.not.be. One = The user ID cannot be 1 userNameCopy the code

2. Create a user class

@notnull (message = "{id.is.null}") private Long ID; @NotBlank(message = "{userName.is.blank}") private String userName; }Copy the code

3. Test method

@RestController public class TestController { @PostMapping //1. Parameter verification match Public Object add(@requestBody@Valid User User) throws Exception{//2. Validationutil.isfalse (objects.equals (user.getid (),1L),"id.is.can.not.be.one"); If (objects.equals (user.getid (),2L)){throw new ServiceException(" user id cannot be 2"); If (objects.equals (user.getid (),3L)){throw new Exception(" user id cannot be 3"); } system.out.println (user.tostring ()); return Result.ok(BaseResult.INSERT_SUCCESS); }}Copy the code

5.4.1. Parameter verification

Post body parameters in {} hit validation rules: 1 the console output: {" code ": 500," errorCode ":".... Id is null, the userName is blank ", "message" : "User ID cannot be empty, user name cannot be empty ", "traceId": null, "data": null}Copy the code

5.4.2. Tool class verification

Check rule: 2 Console output: {"code": 500, "errorCode": "id.is.can.not.be. One ", "message": "User ID cannot be equal to 1", "traceId": null, "data": null}Copy the code

5.4.3. Service exception verification

Check rule: 3 Console output: {"code": 500, "errorCode": null, "message": {"code": 500, "errorCode": null, "message": "User ID cannot be 2", "traceId": null, "data": null}Copy the code

5.4.4. Non-service exception

Body parameter {" ID ":3, "userName":" Baiyin "} Matching rule: 4 Console output: {"code": 500, "errorCode": null, "message": "system error ", "traceId": null, "data": null }Copy the code

5.4.5. Normal execution

Body parameter {" ID ":4, "userName":" Baiyan "} Match verification rule: None Console output: {"code": 200, "errorCode": null, "message": "Added successfully ", "traceId": null, "data": null }Copy the code

5.5. Flow chart of global exception handling

6. Summary

This article details how to gracefully use global exceptions in Spring. The following conclusions and recommendations are made:

1. If the method entry parameter is in the form of body, use spring verification rules to precheck the parameters

2. Reduce if/else logic exceptions and use the logic validation tool class

3. Service exceptions checked internally and externally are captured and returned to the front end

4. Unpredictable exceptions are handled in the highest exception capture class of @ExceptionHandler(throwable.class) at the bottom of the pocket. It is forbidden to directly return unwrapped code exceptions to the front end

5. Do not perform thrown exceptions. Print necessary logs where you capture them for fault locating and tracing

7. Source code acquisition

The core content of this article has been collected on github. If you are interested, you can help yourself:

Github.com/louyanfeng2…

Reference 8.

Blog.csdn.net/writebook20…

Blog.csdn.net/michaelgo/a…

Nine. Contact me

If you think the article is well written, you can like it and comment + follow it

Nailing: louyanfeng25

WeChat: baiyan_lou