Unified return and exception handling commonly used in Java projects

Time:2021-11-14

First create a crud project.

Controller calls service and mapper

The following is replaced by simple code

  • controller
@GetMapping("/getUserById")
    public String getUserById(String id){
        String userById = userService.getUserById(id);
        return userById;
    }
  • service
@Override
    public String getUserById(String id) {
        //Simulated service
        User user = userMapper.selectById(id);
        return user.toString();
    }

Although the above code returns the data to the foreground, it does not handle exceptions and does not have a clear identification to tell the front end whether it succeeded or failed. At this time, we need to encapsulate a unified success or failure flag to tell the foreground how to process the returned data.

Use Lombok to simply encapsulate a commonresponse class (the use of Lombok needs to consider whether the project is really suitable for Lombok, and do not lead to later versions or other hidden pits because of convenient introduction.)

@Data
public class CommonResponse {
    /**
     *The returned business code is used to judge success or failure
     *200 success
     *500 failed
     */
    private String code;

    /**Description*/
    private String massage;

    /**Description*/
    private Object date;

    public CommonResponse(String code, String massage, Object date) {
        this.code = code;
        this.massage = massage;
        this.date = date;
    }

    public static CommonResponse succeed(){
        return getCommonResponse(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMassage(), null);
    }
    public static CommonResponse succeed(Object date){
        return getCommonResponse(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMassage(), date);
    }
    public static CommonResponse succeed(String massage,Object date){
        return getCommonResponse(CodeEnum.SUCCESS.getCode(), massage, date);
    }

    public static CommonResponse error(String massage){
        return getCommonResponse(CodeEnum.ERROR.getCode(), massage, null);
    }
    public static CommonResponse error(String code,String massage){
        return getCommonResponse(code, massage, null);
    }
    public static CommonResponse error(){
        return getCommonResponse(CodeEnum.ERROR.getCode(), CodeEnum.ERROR.getMassage(), null);
    }

    public static CommonResponse getCommonResponse(String code, String massage, Object date){
        return new CommonResponse(code,massage,date);
    }
}

The returned controller uses a unified commonresponse

@GetMapping("/getUserById")
    public CommonResponse getUserById(String id){
        String userById = userService.getUserById(id);
        return CommonResponse.succeed(userById);
    }

return

{
    "code": "200",
    "Mass": "success",
    "Date": "user (id = 1, username = Jiawen 00, password = 1000000, age = 5555)"
}

The above returns are basically in line with expectations, but what happens when an unknown exception occurs in the program.
Service transformation

@Override
    public String getUserByIdException(String id) {
        User user = userMapper.selectById(id);
        //Simulation service exception
        int i=5/0;
        return user.toString();
    }

Controller transformation

@GetMapping("/getUserById")
    public CommonResponse getUserById(String id){
        try{
            String userById = userService.getUserById(id);
            return CommonResponse.succeed(userById);
        }catch(Exception e){
            e.printStackTrace();
            log.error(e.getMessage());
            return CommonResponse.error(e.getMessage());
        }
    }

The above can also be used to judge how to return through trycatch. However, a large number of try catches will appear in the code. Isn’t it very ugly? Can we add a unified try? The answer is yes.

Use the unified exception handling provided by spring
Spring provides three exception capture methods, which I recommend

@Slf4j
@ControllerAdvice
public class ExceptionHandle {
    /**
     *Handle unknown exception
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public CommonResponse handleException(Exception e){
        Log. Error ("system exception: {}", e.getmessage());
        return CommonResponse.error(e.getMessage());
    }

    /**
     *Handle self thrown custom exceptions
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public CommonResponse handleBusinessException(BusinessException e){
 
        Log. Error ("custom exception: {}", e.geterrmassage());
        return CommonResponse.error(e.getErrCode(),e.getErrMassage());
    }
}

Trampled pit

  1. This place encountered a pit when writing. Because the return value after catching an exception is commonresponse, the annotation @ ResponseBody should be added to facilitate format conversion.
  2. Two identical exceptions cannot be configured when configuring @ exceptionhandler. Otherwise, you will not know which to use and report an error.

At this time, the controller is clear as follows: only handle the business call, and there is no need to handle the upward thrown exception.

@GetMapping("/getUserByIdException")
    public CommonResponse getUserByIdException(String id){
        String userById = userService.getUserByIdException(id);
        return CommonResponse.succeed(userById);
    }
    @GetMapping("/getUserByIdBusinessException")
    public CommonResponse getUserByIdBusinessException(String id){
        String userById = userService.getUserByIdBusinessException(id);
        return CommonResponse.succeed(userById);
    }

Of course, sometimes we will encounter our own verification failure and terminate the program. We can throw an exception, or we need to customize the return code, or customize an exception class. The following is a simple example. You can customize it according to your business needs.

  • Custom exception class businessexception
@Data
public class BusinessException extends RuntimeException{

    private static final long serialVersionUID = 918204099850898995L;

    private String errCode;

    private String errMassage;

    public BusinessException(String errCode,String errMassage){
        super(errMassage);
        this.errCode = errCode;
        this.errMassage = errMassage;
    }
}
  • Custom exception returned by service
@Override
    public String getUserByIdBusinessException(String id) {
        User user = userMapper.selectById(id);
        //Simulation service exception
        if("1".equals(id)){
            Throw new businessexception ("400", "data with ID 1 does not support query");
        }
        return user.toString();
    }

At this point, we get the return from the front end

##Request
http://localhost:8088/getUserByIdBusinessException?id=1
##Return

{
    "code": "400",
    "Mass": "query is not supported for data with ID 1",
    "date": null
}

The above is unified exception capture and unified return.

In addition, in our actual project, in order to make the business exceptions beautiful and unified, we can define an enumeration to store our business exception information.

  1. Define an enumeration class
public enum ExceptionEnum {

    BUSINESS_ NOT_ One ("400", "data with ID 1 does not support query"),
    ERR_ NOT_ Look ("401", "too handsome to see")
    //Accumulate back
    ;

    private String code;
    private String desc;

    ExceptionEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    public void ThrowException(){
        ThrowException(code,desc);
    }
    public void ThrowException(String errMassage){
        errMassage = desc +":"+errMassage;
        ThrowException(code,errMassage);
    }
    private BusinessException ThrowException(String code,String desc){
        throw new BusinessException(code,desc);
    }
}
  1. An enumeration exception is thrown in the service
@Override
    public String getUserByIdBusinessExceptionByEnumOne(String id) {
        User user = userMapper.selectById(id);
        //Simulation service exception
        if("1".equals(id)){
            ExceptionEnum.BUSINESS_NOT_ONE.ThrowException();
        }
        return user.toString();
    }
    @Override
    public String getUserByIdBusinessExceptionByEnumTwo(String id) {
        User user = userMapper.selectById(id);
        //Simulation service exception
        if("look".equals(id)){
            //Can dynamically splice exception information
            ExceptionEnum.ERR_ NOT_ Look. Throwexception ("are you right" + ID);
        }
        return user.toString();
    }
  1. Foreground return
{
    "code": "400",
    "Mass": "data with ID 1 does not support querying",
    "date": null
}

{
    "code": "401",
    "Massage": "too handsome to see: are you right, look",
    "date": null
}

The advantage of this approach is to facilitate management and know at a glance how many errors and exceptions there are in your project. However, some students will find it hard to write like this. Each time they throw an exception, they must first define it in the enumeration class. Therefore, this practice depends on the project and members.

The above is my humble opinion. If there is anything inappropriate, please leave a message for correction.

From project to project