SpringBoot global exception handling Json + template page


Article reference
spring-boot-demo-exception-handler
github jump

We will encounter many exceptions in our project. We should catch exceptions in time, but inevitably, there will be many unexpected exceptions. Therefore, we should customize the global exceptions and deal with these exception information uniformly

1. Project construction

Create a new SpringBoot project and introduce lombok, web, thymeleaf and other dependencies

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--There is also a dependency on hot deployment. You can add it or not-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

2. Unified return results

2.1 unified result return class

Just take a few glances. It's easy to use. The Status is below

@Data
public class ApiResponse {
	/**
	 * Status code
	 */
	private Integer code;

	/**
	 * Return content
	 */
	private String message;

	/**
	 * Return data
	 */
	private Object data;

	/**
	 * non-parameter constructor 
	 */
	private ApiResponse() {

	}

	/**
	 * All parameter constructor
	 *
	 * @param code    Status code
	 * @param message Return content
	 * @param data    Return data
	 */
	private ApiResponse(Integer code, String message, Object data) {
		this.code = code;
		this.message = message;
		this.data = data;
	}

	/**
	 * Construct a custom API return
	 *
	 * @param code    Status code
	 * @param message Return content
	 * @param data    Return data
	 * @return ApiResponse
	 */
	public static ApiResponse of(Integer code, String message, Object data) {
		return new ApiResponse(code, message, data);
	}

	/**
	 * Construct a successful API return with data
	 *
	 * @param data Return data
	 * @return ApiResponse
	 */
	public static ApiResponse ofSuccess(Object data) {
		return ofStatus(Status.OK, data);
	}

	/**
	 * Construct an API return of a successful and customized message
	 *
	 * @param message Return content
	 * @return ApiResponse
	 */
	public static ApiResponse ofMessage(String message) {
		return of(Status.OK.getCode(), message, null);
	}

	/**
	 * Construct a stateful API return
	 *
	 * @param status Status {@ link Status}
	 * @return ApiResponse
	 */
	public static ApiResponse ofStatus(Status status) {
		return ofStatus(status, null);
	}

	/**
	 * Construct a stateful API return with data
	 *
	 * @param status Status {@ link Status}
	 * @param data   Return data
	 * @return ApiResponse
	 */
	public static ApiResponse ofStatus(Status status, Object data) {
		return of(status.getCode(), status.getMessage(), data);
	}

	/**
	 * Construct an API return with exception and data
	 *
	 * @param t    abnormal
	 * @param data Return data
	 * @param <T>  {@link BaseException} Subclass of
	 * @return ApiResponse
	 */
	public static <T extends BaseException> ApiResponse ofException(T t, Object data) {
		return of(t.getCode(), t.getMessage(), data);
	}

	/**
	 * Construct an API return with exception and data
	 *
	 * @param t   abnormal
	 * @param <T> {@link BaseException} Subclass of
	 * @return ApiResponse
	 */
	public static <T extends BaseException> ApiResponse ofException(T t) {
		return ofException(t, null);
	}
}

2.2 status code

You can expand yourself, just these two for the time being

@Getter
public enum Status {
    /**
     * Operation succeeded
     */
    OK(200, "Operation succeeded"),

    /**
     * Unknown exception
     */
    UNKNOWN_ERROR(500, "Server error");
    /**
     * Status code
     */
    private Integer code;
    /**
     * content
     */
    private String message;

    Status(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

3. Exception related

3.1 user defined basic exception

Inherits the RuntimeException to serve the following custom exceptions

Students who are not clear about @ EqualsAndHashCode

You can see the explanation of this article

Use of @ EqualsAndHashCode(callSuper = false) of Lombok

@Data
@EqualsAndHashCode(callSuper = true)
public class BaseException extends RuntimeException {
    private Integer code;
    private String message;

    public BaseException(Status status) {
        super(status.getMessage());
        this.code = status.getCode();
        this.message = status.getMessage();
    }

    public BaseException(Integer code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }
}

3.2 Json format exception handling

Recommended for front and rear end separation

@Getter
public class JsonException extends BaseException {

    public JsonException(Status status) {
        super(status);
    }

    public JsonException(Integer code, String message) {
        super(code, message);
    }
}

3.3 template page exception handling

Used for thymeleaf page processing to return static pages

@Getter
public class PageException extends BaseException {

    public PageException(Status status) {
        super(status);
    }

    public PageException(Integer code, String message) {
        super(code, message);
    }
}

4. Core: unified exception handling

@ControllerAdvice+@ExceptionHandler are a perfect match

The former is added to the class to specify that the class is an exception handling class

The latter one is added to the method to specify what exception types are caught

ps: of course you can add an Exception class to handle all exceptions

4.2 handling json + page exceptions

@ControllerAdvice
@Slf4j
public class DemoExceptionHandler {
	private static final String DEFAULT_ERROR_VIEW = "error";

	/**
	 * Unified json exception handling
	 *
	 * @param exception JsonException
	 * @return Unified return json format
	 */
	@ExceptionHandler(value = JsonException.class)
	@ResponseBody
	public ApiResponse jsonErrorHandler(JsonException exception) {
		log.error("[JsonException]:{}", exception.getMessage());
		return ApiResponse.ofException(exception);
	}

	/**
	 * Unified page exception handling
	 *
	 * @param exception PageException
	 * @return Unified jump to exception page
	 */
	@ExceptionHandler(value = PageException.class)
	public ModelAndView pageErrorHandler(PageException exception) {
		log.error("[DemoPageException]:{}", exception.getMessage());
		ModelAndView view = new ModelAndView();
		view.addObject("code", exception.getCode());
		view.addObject("message", exception.getMessage());
		view.setViewName(DEFAULT_ERROR_VIEW);
		return view;
	}
}

4.2 error template page error html

Don't put it in the wrong position
In the src/main/resources/templates directory

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
    <meta charset="UTF-8"/>
    <title>Unified page exception handling</title>
</head>
<body>
<h1>Unified page exception handling</h1>
<div th:text="'Error code:'+${code}"></div>
<div th:text="'Error message:'+${message}"></div>
</body>
</html>

5. controller test

@Controller
@Slf4j
public class BbController {

    @GetMapping("hello")
    @ResponseBody
    public ApiResponse hello() {
    	// If the result is returned normally, we don't need to throw an exception. We can use ApiResponse directly
        return ApiResponse.ofStatus(Status.OK,"hello bb");
    }
    @GetMapping("hello2")
    @ResponseBody
    public ApiResponse hello2() {
    	// Directly return unknown error json format
        throw new JsonException(Status.UNKNOWN_ERROR);
    }
    @GetMapping("hello3")
    //No need to add @ ResponseBody
    public String hello3() {
    	//The static page error is returned html
        throw new PageException(Status.UNKNOWN_ERROR);
    }
}

It should be noted that if you return a template page, you need to introduce the thymeleaf dependency and do not need to add @ ResponseBody

5.1 access http://localhost:8080/hello

5.2 access http://localhost:8080/hello2

You can see the information printed on the console to facilitate our troubleshooting

2021-08-01 11:41:00.355 ERROR 3284 --- [nio-8080-exec-1] c.b.e.exception.DemoExceptionHandler     : [JsonException]:Server error
2021-08-01 11:41:00.372  WARN 3284 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [BaseException(code=500, message=Server error)]

5.3 access http://localhost:8080/hello3


The information printed on the console is the same. There is no screenshot here

One last point
Unified exception handling can only handle exceptions at the Controller level
For the exception caused by the mapper service, either throw it to the controller layer
Or try catch and handle it yourself

Keywords: Spring Boot

Added by thefreebielife on Tue, 04 Jan 2022 12:29:06 +0200