This article mainly records how to use the ErrorController interface capability provided by SpringBoot; It has a built-in BasicErrorController to handle exceptions uniformly, The Controller automatically forwards requests to /error request path when an exception occurs (/error is a default mapping provided by SpringBoot). BasicErrorController provides two types of return errors: 1. 2. Json returns.

background

A problem encountered during development: All REST requests in the project are returned as JSON with a custom uniform data structure object, as follows:

public class Response<T{

    / / data

    private T data;

    / / success

    private boolean success;

    // Exception information

    private String error;

    // omit get set

}

Copy the code

This structure is very common, and I’m sure many developers have played with it. All results returned by REST requests in the project are returned as Response objects, as follows:

@RequestMapping("test")

public Response<String> testApi(a){

    Response<String> result = new Response<>();

    result.setData("this is glmapper blog");

    result.setSuccess(true);

    return result;

}

Copy the code

This is basically a simplified version of the model; For security reasons, there is now a requirement to verify each request, such as whether the request carries a token. The idea is simply to intercept a request by means of an interceptor or filter.

In fact, whether interceptors or filters, one problem to consider is how to return the exception information in the uniform data format specified in the project, namely, return Response, in the case of checksum failure or exception generated during checksum.

So let me just write Response back

Using the PrintWriter provided in the ServletResponse, print the Response directly back in JSON format. The approximate code is as follows:

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,

                        FilterChain chain)
 throws IOException, ServletException 
{

    HttpServletRequest request = (HttpServletRequest) servletRequest;

    String requestURI = request.getRequestURI();

    // Mock the exception request

    if (requestURI.contains("testTokenError")) {

        Response<String> response = new Response<>();

        response.setError("token validation fails");

        // Write back the exception message

        returnResponse((HttpServletResponse)servletResponse,JSONObject.toJSONString(response));

        / / return

        return;

    }

    chain.doFilter(servletRequest, servletResponse);

}



private void returnResponse(HttpServletResponse response, String data) {

    PrintWriter writer = null;

    response.setCharacterEncoding("UTF-8");

    response.setContentType("text/html; charset=utf-8");

    try {

        writer = response.getWriter();

        // Print data directly through PrintWriter

        writer.print(data);

    } catch (IOException e) {

    } finally {

        if(writer ! =null)

            writer.close();

    }

}

Copy the code

In this way, it is relatively simple and direct. Print the abnormal data and return it directly, instead of continuing the filter chain.

Throws an exception, handled by BasicErrorController

This approach takes advantage of SpringBoot’s own capabilities to handle error messages more elegantly. The code looks like this:

1. Throw an exception directly in Filter

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,

                        FilterChain chain)
 throws IOException, ServletException 
{

    HttpServletRequest request = (HttpServletRequest) servletRequest;

    String requestURI = request.getRequestURI();

    // Mock the exception request

    if (requestURI.contains("testTokenError")) {

        // Return a custom exception directly

        throw new ValidationException("token validation fails");

    }

    chain.doFilter(servletRequest, servletResponse);

}

Copy the code

2. Define an exception handling Controller

Define a TokenErrorController that inherits the BasicErrorController class provided by SpringBoot, and override the error method (or errorHtml if it’s a page). Used to return custom Response data. The code is as follows:

@RestController

public class TokenErrorController extends BasicErrorController {

    // Override the error method

    @Override

    @RequestMapping(produces = { MediaType.APPLICATION_JSON_VALUE })

    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {

        Map<String, Object> body = getErrorAttributes(request,

            isIncludeStackTrace(request, MediaType.ALL));

        HttpStatus status = getStatus(request);

        // Get the exception message in the body

        String message = body.get("message").toString();

        // Build the Response object

        Response response = new Response();

        // Set message to Response

        response.setError(message);

        / / return

        return new ResponseEntity(response, status);

    }

    // omit other extraneous code

}

Copy the code

This makes it possible to handle only exceptions thrown in the additional Filter without changing any of the previous project code. It is important to note that the BasicErrorController receives the exception message thrown by Filter, and then wraps the exception message and returns it. Why should I mention this? This is to distinguish it from the @ControllerAdvice and @RestControllerAdvice annotations provided in SpringBoot for handling global exceptions. In SpringBoot, these two annotations can be used to implement global interception of classes annotated by @Controller and @RestController. Since it is AOP interception at the Controller level, exceptions thrown by Filter can be implemented by using these annotations. Global exception handlers defined with @ControllerAdvice and @RestControllerAdvice annotations are not handled.

Here’s a quick look at the use of the @ControllerAdvice and @RestControllerAdvice annotations.

Global exception handling

Define a custom OtherExcepetion, and then write a global exception handler based on the @RestControllerAdvice annotation.

@RestControllerAdvice

public class OtherExceptionHandler {

    // Only OtherException is handled here

    @ExceptionHandler(value = OtherException.class)

    public Response<String> otherExceptionHandler(HttpServletRequest req, OtherException e){

        Response response = new Response();

        response.setError(e.getMessage());

        return response;

    }

    // Of course you can also define @ExceptionHandler to handle other exceptions

}

Copy the code

This method does not handle Filter exceptions, only Controller exceptions.

summary

This article mainly records how to ensure that exceptions thrown by Filter in SpringBoot can be returned in the same type of object as business, and provides a brief introduction to SpringBoot based on the Controller layer exception capture processing. They handle exceptions differently:

  • BasicErrorController: Receives an exception request from /error. The Filter throws an exception forward to /error and then handles it.
  • RestControllerAdvice: By AOP intercepting all classes annotated by the @Controller annotation, processing can be done based on the exception type matching the specific ExceptionHandler.

The level is limited, if the article expresses the wrong place, hopes each big guy to give correction ~