SpringBoot: how to gracefully pass parameters, encapsulate response data, and handle exceptions?

Posted by Deadman2 on Mon, 25 Oct 2021 15:00:37 +0200

In project development, JSON format is used for data transmission between interfaces and between front and back ends.

1 fastjson usage

Alibaba's fastjson is currently the most widely used JSON parsing framework. This article will also use fastjson.

1.1 introducing dependencies

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.35</version>
</dependency>

2. Uniformly encapsulate the returned data

In a web project, the data returned by the interface generally includes status code, information, data, etc. for example, the following interface example:

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author guozhengMu
 * @version 1.0
 * @date 2019/8/21 14:55
 * @description
 * @modify
 */
@RestController
@RequestMapping(value = "/test", method = RequestMethod.GET)
public class TestController {
    @RequestMapping("/json")
    public JSONObject test() {
        JSONObject result = new JSONObject();
        try {
            // Business logic code
            result.put("code", 0);
            result.put("msg", "Operation succeeded!");
            result.put("data", "test data");
        } catch (Exception e) {
            result.put("code", 500);
            result.put("msg", "System exception, please contact the administrator!");
        }
        return result;
    }
}

In this case, each interface is handled in this way, which is very troublesome and needs a more elegant implementation

2.1 define a unified JSON structure

Attributes in the unified JSON structure include data, status code and prompt information. Other items can be added as needed. Generally speaking, there should be a default return structure and a user specified return structure. Since the return data type cannot be determined, a generic type needs to be used. The code is as follows:

public class ResponseInfo<T> {
    /**
     * Status code
     */
    protected String code;
    /**
     * Response information
     */
    protected String msg;
    /**
     * Return data
     */
    private T data;

    /**
     * If no data is returned, the default status code is 0 and the prompt message is "operation succeeded!"
     */
    public ResponseInfo() {
        this.code = 0;
        this.msg = "Operation succeeded!";
    }

    /**
     * If no data is returned, you can manually specify the status code and prompt information
     * @param code
     * @param msg
     */
    public ResponseInfo(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * When data is returned, the status code is 0, and the default prompt message is "operation succeeded!"
     * @param data
     */
    public ResponseInfo(T data) {
        this.data = data;
        this.code = 0;
        this.msg = "Operation succeeded!";
    }

    /**
     * There is data return, the status code is 0, and the prompt information is manually specified
     * @param data
     * @param msg
     */
    public ResponseInfo(T data, String msg) {
        this.data = data;
        this.code = 0;
        this.msg = msg;
    }
    // Omit the get and set methods
}

2.2 use unified JSON structure

After encapsulating the unified return data structure, we can use it directly in the interface. As follows:

import com.example.demo.model.ResponseInfo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author guozhengMu
 * @version 1.0
 * @date 2019/8/21 14:55
 * @description
 * @modify
 */
@RestController
@RequestMapping(value = "/test", method = RequestMethod.GET)
public class TestController {
    @RequestMapping("/json")
    public ResponseInfo test() {
        try {
            // Simulation exception service code
            int num = 1 / 0;
            return new ResponseInfo("test data");
        } catch (Exception e) {
            return new ResponseInfo(500, "System exception, please contact the administrator!");
        }
    }
}

As mentioned above, the return data processing of the interface is much more elegant. Test the above interface, start the project, visit localhost:8096/test/json through the browser, and get the response results:

{"code":500,"msg":"System exception, please contact the administrator!","data":null}

3. Global exception handling

3.1 system definition exception handling

Create a new ExceptionHandlerAdvice global exception handling class, and then add @ RestControllerAdvice annotation to intercept the exceptions thrown in the project. The following code contains several exception handling, such as parameter format exception, parameter missing, system exception, etc., as shown in the following example:

@RestControllerAdvice
@Slf4j
public class ExceptionHandlerAdvice {

    // Parameter format exception handling
    @ExceptionHandler({IllegalArgumentException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseInfo badRequestException(IllegalArgumentException exception) {
    	log.error("Illegal parameter format:" + e.getMessage());
        return new ResponseInfo(HttpStatus.BAD_REQUEST.value() + "", "Parameter format does not match!");
    }

	// Insufficient permission exception handling
    @ExceptionHandler({AccessDeniedException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ResponseInfo badRequestException(AccessDeniedException exception) {
        return new ResponseInfo(HttpStatus.FORBIDDEN.value() + "", exception.getMessage());
    }

	// Parameter missing exception handling
    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseInfo badRequestException(Exception exception) {
        return new ResponseInfo(HttpStatus.BAD_REQUEST.value() + "", "Missing required parameter!");
    }

    // Null pointer exception
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo handleTypeMismatchException(NullPointerException ex) {
        log.error("Null pointer exception,{}", ex.getMessage());
        return new JsonResult("500", "Null pointer exception");
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public JsonResult handleUnexpectedServer(Exception ex) {
        log.error("System exception:", ex);
        return new JsonResult("500", "An exception occurred in the system. Please contact the administrator");
    }
    
    // System exception handling
    @ExceptionHandler(Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo exception(Throwable throwable) {
        log.error("System exception", throwable);
        return new ResponseInfo(HttpStatus.INTERNAL_SERVER_ERROR.value() + "System exception, please contact the administrator!");
    }
}
  • @The RestControllerAdvice annotation contains the @ Component annotation, which indicates that when Spring Boot starts, this class will also be handed over to Spring as a component for management.
  • @The RestControllerAdvice annotation contains the @ ResponseBody annotation to output JSON formatted encapsulated data to the caller after exception handling.
  • @The RestControllerAdvice annotation also has a basePackages attribute, which is used to intercept the exception information in which package. Generally, we do not specify this attribute, but we intercept all exceptions in the project.
  • Specify the specific exception on the method through the @ ExceptionHandler annotation, then process the exception information in the method, and finally return the result to the caller through the unified JSON structure.
  • However, in the project, we usually intercept some common exceptions in detail. Although intercepting exceptions can be done once and for all, it is not conducive for us to troubleshoot or locate problems. In the actual project, you can write the intercepted Exception at the bottom of GlobalExceptionHandler. If it is not found, finally intercept the Exception to ensure that the output information is friendly.

Let's test through an interface:

@RestController
@RequestMapping(value = "/test", method = RequestMethod.POST)
public class TestController {
    @RequestMapping("/json")
    public ResponseInfo test(@RequestParam String userName, @RequestParam String password) {
        try {
            String data = "Login user:" + userName + ",password:" + password;
            return new ResponseInfo("0", "Operation succeeded!", data);
        } catch (Exception e) {
            return new ResponseInfo("500", "System exception, please contact the administrator!");
        }
    }
}

Interface call, password is intentionally left blank:

3.2 custom exception interception

In the actual project, in addition to intercepting some system exceptions, in some businesses, we need to customize some business exceptions. When we want to handle the call of a service, the call may fail or timeout. At this time, we need to customize an exception, throw the exception when the call fails, and let the ExceptionHandlerAdvice catch it.

3.2.1 define exception information

Because there are many exceptions in business, the above system defined exceptions are far from being covered. In order to facilitate project exception information management, we generally define an exception information enumeration class. For example:

public enum BusinessMsgEnum {
    /**
     * Parameter exception
     */
    PARMETER_EXCEPTION("101", "Parameter exception!"),
    /**
     * Wait timeout
     */
    SERVICE_TIME_OUT("102", "Service timeout!"),
    /**
     * Parameter too large
     */
    PARMETER_BIG_EXCEPTION("903", "The content cannot exceed 200 words. Please try again!"),
    /**
     * Database operation failed
     */
    DATABASE_EXCEPTION("509", "Database operation is abnormal, please contact the administrator!"),
    /**
     * 500 : Once and for all tips can also be defined here
     */
    UNEXPECTED_EXCEPTION("500", "The system is abnormal, please contact the administrator!");
    // You can also define more business exceptions

    /**
     * Message code
     */
    private String code;
    /**
     * Message content
     */
    private String msg;

    private BusinessMsgEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    // set get method
}

3.2.2 intercept custom exceptions

We can define a business exception. When a business exception occurs, we can throw the custom business exception. For example, we define a BusinessErrorException exception as follows:

public class BusinessErrorException extends RuntimeException {

    private static final long serialVersionUID = -7480022450501760611L;

    /**
     * Exception code
     */
    private String code;
    /**
     * Exception prompt information
     */
    private String msg;

    public BusinessErrorException(BusinessMsgEnum businessMsgEnum) {
        this.code = businessMsgEnum.code();
        this.msg = businessMsgEnum.msg();
    }
    // get set method
}

In the construction method, pass in the above customized exception enumeration class. In the project, if new exception information needs to be added, we can directly add it in the enumeration class. It is very convenient to achieve unified maintenance and obtain it when intercepting the exception.

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * Intercept business exceptions and return business exception information
     * @param ex
     * @return
     */
    @ExceptionHandler(BusinessErrorException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseInfo handleBusinessError(BusinessErrorException ex) {
        String code = ex.getCode();
        String message = ex.getMessage();
        return new ResponseInfo(code, message);
    }
}

At the interface layer, simulate the exception scenario as follows:

@RestController
@RequestMapping("/test")
public class ExceptionController {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class);

    @GetMapping("/exception")
    public ResponseInfo testException() {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new BusinessErrorException(BusinessMsgEnum.UNEXPECTED_EXCEPTION);
        }
        return new ResponseInfo();
    }
}

Start the project and request the interface:

Author: Yunshen i doesn't know where

Original link:
https://blog.csdn.net/mu_wind/article/details/99960645

Topics: JSON Spring Boot RESTful