Spring Boot 面试题——全局异常处理

目录

  • 1.@ControllerAdvice 注解与 @RestControllerAdvice 注解有什么区别?
  • 2.@ExceptionHandler 注解有什么作用?
  • 3.当有多个异常时,@ExceptionHandler 捕获哪一个?
  • 4.ErrorController 接口有什么作用?
  • 5.在 Spring Boot 中如何实现全局异常处理?
    • 5.1.使用 @(Rest)ControllerAdvice 注解与 @ExceptionHandler 注解
    • 5.2.实现 ErrorController 接口
    • 5.3.上述两种方式的区别

1.@ControllerAdvice 注解与 @RestControllerAdvice 注解有什么区别?

(1)在 Spring Boot 中,@ControllerAdvice 注解和 @RestControllerAdvice 注解都可以用于定义全局异常处理器类,它们在实现上没有实质性的区别,只是对返回值类型有略微的不同要求:

  • @ControllerAdvice 注解用于处理 Spring MVC 或 Thymeleaf 渲染的网页请求(即使用视图解析器渲染 HTML 等模板),处理完成后通常返回视图或者 HTML 页面。
  • @RestControllerAdvice 注解则用于处理 RESTful 模式下的访问请求,通过 HTTP 协议传输 JSON 或 XML 格式的数据,处理完成后通常返回 JSON 或 XML 数据。

(2)两者的区别在于,@ControllerAdvice 需要返回视图或 HTML 页面,所以处理器方法必须返回 ModelAndView 对象或者 String 类型的视图名称;而 @RestControllerAdvice 只需要返回纯数据(JSON 或 XML 格式的数据),所以处理器方法可以直接返回对象、字符串等数据类型。具体实现上只要在类上标注相应的注解,处理器方法上都可以直接使用 @ExceptionHandler 注解,为指定的异常类型提供特定的处理逻辑。

(3)举个例子,下面是一个 @ControllerAdvice 注解的全局异常处理器:

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ModelAndView handleException(Exception e) {
        ModelAndView mav = new ModelAndView();
        mav.addObject("message", e.getMessage());
        mav.setViewName("error");
        return mav;
    }
}

这个处理器里面的处理器方法 handleException 返回值类型为 ModelAndView,即为视图和模型的封装对象。而下面是一个 @RestControllerAdvice 注解的全局异常处理器:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
    }

}

这个处理器里面的处理器方法 handleException 直接返回了 ResponseEntity 类型的对象,即为返回值类型包含了 HTTP 响应头、响应状态码和返回内容三个“部分”的响应实体。这种写法适合处理 JSON 数据等纯数据相关的请求。

2.@ExceptionHandler 注解有什么作用?

(1)在 Spring Framework 中,@ExceptionHandler 注解用来指定某个方法处理特定异常或一组异常。当被注解的方法所抛出的异常符合某个请求抛出的异常时,Spring Framework 就会调用这个被注解的方法来处理该异常,然后返回一个对用户友好的错误提示信息。因此,使用 @ExceptionHandler 注解可以很方便地进行全局异常处理、处理特定的异常或者优化异常信息,使程序更健壮、更可靠

(2)具体使用方式如下:

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ModelAndView handleAllException(Exception ex) {
        ModelAndView model = new ModelAndView("error_page");
        model.addObject("errMsg", "this is a generic exception");
        return model;
    }
 
    @ExceptionHandler(IOException.class)
    public String handleIOException() {
        return "home";
    }
}

在上面的实例中,第一个方法用于处理通用的异常,并返回一个视图名为 error_page 的 ModelAndView。其中 addAttribute 方法用来将异常信息添加到 ModelAndView 中,供 error_page 视图显示。第二个方法处理 IOException 异常,并将视图名直接返回为 home。

需要注意的是,@ExceptionHandler 注解的处理方法可以有多个,每个方法负责一个相应的异常类型,也可以进行一些个性化的处理。如果抛出的异常没有找到相应的处理方法,那么 Spring Framework 就会继续查找其他注解了 @ExceptionHandler 的类中是否有对该异常类型进行处理的方法。

(3)总的来说,@ExceptionHandler 注解是一个非常方便的全局异常处理方案,使得应用程序能够自定义处理程序所抛出的各种异常、错误信息,提供更好的用户友好的提示信息,提高程序的健壮性和可靠性。

3.当有多个异常时,@ExceptionHandler 捕获哪一个?

(1)当有多个异常时,@ExceptionHandler 注解会根据异常类型的继承关系或实现关系来确定哪一个异常处理方法能够被调用。在查找这些方法的过程中,Spring 会从自身所表示的异常类型开始,依次遍历其继承的所有异常类型,直到找到与当前抛出的异常类型最为匹配的处理方法。

(2)举个例子,我们有两个异常处理方法:

@ExceptionHandler(FileNotFoundException.class)
public void handleFileNotFoundException(FileNotFoundException ex) {
    ...
}

@ExceptionHandler(IOException.class)
public void handleIOException(IOException ex) {
    ...
}

当程序中抛出 FileNotFoundException 异常时,@ExceptionHandler 注解会优先匹配处理 FileNotFoundException 异常的方法,找到对应方法后就执行该方法中定义的逻辑;如果没有找到匹配的方法,则会继续查找其父类所对应的异常类型,直到找到能够处理该异常的方法为止。在这个例子中,FileNotFoundException 是 IOException 的子类,因此会先匹配 handleFileNotFoundException() 方法,如果没有找到,就会去查找 handleIOException() 方法来处理异常。

(3)需要注意的是,当异常类型之间没有继承关系或实现关系时,不管在注解中声明的顺序如何,都无法保证哪个异常处理方法会被执行。此时,最好编写多个 @ExceptionHandler 方法来处理各个不同的异常类型。

4.ErrorController 接口有什么作用?

(1)在 Spring Boot 中,ErrorController 接口属于 Spring MVC 的组件之一,它的主要作用是处理应用程序中的异常,将异常处理结果按照指定的格式返回给客户端。根据应用业务场景的不同,我们可以通过实现 ErrorController 接口或者继承 BasicErrorController 类来自定义处理方法和返回结果。

(2)具体来说,ErrorController 主要负责处理 HTTP 请求中出现的错误,通常包括 401、404、500 等状态码。我们可以在代码中实现 ErrorController 来自定义这些状态码的错误信息和响应。举个例子,下面是一个自定义 404 错误页面和错误消息的 ErrorController 实现:

@Controller
public class MyErrorController implements ErrorController {
    @RequestMapping("/error")
    public String handleError(HttpServletRequest request, Model model) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if(statusCode == 404) {
            model.addAttribute("errorMsg", "您访问的页面不存在");
            return "404";
        }
        model.addAttribute("errorMsg", "服务器出错了");
        return "500";
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }
}

这个自定义的 MyErrorController 实现了 ErrorController 接口,并重写了其中的 handleError() 方法。在方法中,我们通过 HttpServletRequest 来获取请求的错误状态码,并对 404 和 500 状态码分别返回对应的错误视图。另外,我们还可以在 model 中添加自定义的错误消息,以便在视图中显示。

(3)值得注意的是,自定义的 ErrorController 可以被多个应用程序共享,但是如果在同一应用程序的不同控制器中也定义了相同的错误处理逻辑,那么这些逻辑会被优先执行,而 ErrorController 中的处理逻辑则不会生效。因此,我们建议使用 @ControllerAdvice 注解来实现全局的异常处理。

5.在 Spring Boot 中如何实现全局异常处理?

5.1.使用 @(Rest)ControllerAdvice 注解与 @ExceptionHandler 注解

可以使用 @ControllerAdvice 注解来定义一个全局异常处理器类,然后在这个类中使用 @ExceptionHandler 注解来定义方法,处理我们需要捕获的异常类型。举个例子:

@ControllerAdvice
public class GlobalExceptionHandler {
	
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public String handleRuntimeException(RuntimeException ex) {
        return "RuntimeException: " + ex.getMessage();
    }

    @ExceptionHandler(IOException.class)
    @ResponseBody
    public String handleIOException(IOException ex) {
        return "IOException: " + ex.getMessage();
    }
}

当运行时或 I/O 异常被抛出时,上面的方法会通过 @ExceptionHandler 注解被调用来处理异常。@ResponseBody 注解表示返回值将以 JSON 或 XML 格式写入响应体。这些方法可以根据自己的需求来返回不同的结果。

在 Spring Boot 中,@ControllerAdvice 注解标注的全局异常处理器类是通过 @Component 注解自动被注册到 Spring 容器中,无需手动在配置文件中进行注册。具体实现原理是在 Spring Boot 中启用了自动化配置机制,对于标注了 @ControllerAdvice 注解的类,Spring Boot 会自动将它们注册到容器中,并且它们会被自动应用于应用程序中,对异常进行统一处理。当应用程序抛出异常时,Spring Boot 会调用这些已注册到容器中的全局异常处理器的相关方法,对异常进行处理,并返回相关错误信息。

5.2.实现 ErrorController 接口

我们也可以实现 ErrorController 接口来处理全局异常。这个接口有一个 handle() 方法,它会默认在错误发生时被调用。我们可以实现它并在这个方法中根据具体错误类型来做响应的处理。代码示例如下:

@Controller
@RequestMapping("/error")
public class MyErrorController implements ErrorController {

    private final static String ERROR_PATH = "/error";

    @RequestMapping(produces = {MediaType.TEXT_HTML_VALUE})
    public ModelAndView errorHtml(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("message", "Something is wrong");
        return modelAndView;
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        Map<String, Object> error = new HashMap<>();
        error.put("status", status.value());
        error.put("message", "Something is wrong");
        return new ResponseEntity<Map<String, Object>>(error, status);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        try {
            return HttpStatus.valueOf(statusCode);
        }
        catch (Exception ex) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }

}

上述代码为一个实现了 ErrorController 接口的类,其中 errorHtml() 和 error() 方法分别用于处理 HTML 请求和其他请求。当错误状态码不为 404 时,将返回一个 ModelAndView 以显示错误信息,并用 errorHtml() 方法可以自定义模板。当请求的 MIME 类型不为 “text/html” 且状态码为 404 时,将返回一个 JSON 对象,提供错误状态码和错误消息。

需要注意的是,ErrorController 只能处理那些没有被控制器处理的请求。如果请求已经被控制器处理并返回结果,那么这种方式不会生效。因此,在这种情况下,我们可以使用 @ControllerAdvice 的方式来实现全局异常处理

5.3.上述两种方式的区别

  • @ControllerAdvice 方式只能处理控制器抛出的异常。此时请求已经进入控制器中。
  • ErrorController 类方式可以处理所有的异常,包括未进入控制器的错误,比如 404、401 等错误
  • 如果应用中两者共同存在,则 @ControllerAdvice 方式处理控制器抛出的异常,ErrorController类 方式处理未进入控制器的异常。
  • @ControllerAdvice 方式可以定义多个拦截方法,拦截不同的异常类,并且可以获取抛出的异常信息,自由度更大。

你可能感兴趣的:(Java,后端面试,spring,boot)