正常情况下,如果访问发生异常错误,Spring Boot自动配置的默认错误处理器会查找名为error的视图,如果找不到就用默认的白标错误视图,如下图所示。
如果我们在/templates/error
目录下预先放置了404.html
500.html
等错误静态页面,Springboot会自动进行对应跳转,显得十分便利。
而 为了自定义错误界面,我们可能需要对Controller类产生的异常进行拦截,在此我们使用到了两个注解:@ControllerAdvice
@ExceptionHandler
@ControllerAdvice
注解
@ControllerAdvice
是控制器增强注解。可以用于定义@ExceptionHandler
、@InitBinder
、@ModelAttribute
,并应用到所有@RequestMapping
中。
启动应用后,被 @ExceptionHandler
、@InitBinder
、@ModelAttribute
注解的方法,都会作用在 被 @RequestMapping
注解的方法上。
@ExceptionHandler
拦截了异常,我们可以通过该注解实现自定义异常处理。其中,@ExceptionHandler
配置的 value 指定需要拦截的异常类型,下面拦截了 Exception.class 这种异常。
代码实例
全局处理
Handler类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.ray.blog.handler;
import lombok.extern.log4j.Log4j2; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; @ControllerAdvice @Log4j2 public class ControllerExceptionHandler { @ExceptionHandler(Exception.class) public ModelAndView exceptionHandler(HttpServletRequest httpServletRequest, Exception e) throws Exception { log.error("find exception:e={}",e.getMessage()); ModelAndView mv = new ModelAndView(); mv.addObject("url", httpServletRequest.getRequestURL()); mv.addObject("exception", e); mv.setViewName("error/error"); return mv; } }
|
我们故意将Controller类中加入报错代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.ray.blog.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping;
@Controller public class IndexController { @GetMapping("/") public String index(){ int a = 7/0; return "index"; } }
|
访问127.0.0.1:8080
后,产生异常,随后被拦截重定向到error页面,达到了我们的预期效果
为了调试方便,我们将error.html
套用了Thymeleaf,以注释的方式将具体异常信息注入到网页中
页面如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>error</title> </head> <body> <h1>error</h1> <div th:utext="${exception.message}"></div> <div> <div th:utext="'<!--'" th:remove="tag"></div> <div th:utext="'Failed Request URL: ' + ${url}" th:remove="tag"></div> <div th:utext="'ExceptionMessage: ' + ${exception.message}" th:remove="tag"></div> <ul th:remove="tag"> <li th:each="st : ${exception.stackTrace}" th:remove="tag"> <span th:utext="${st}" th:remove="tag"></span> </li> </ul> <div th:utext="'-->'" th:remove="tag"></div> </div> </body> </html>
|
效果如下:
日志信息:
1 2 3 4
| 21:07:40.673 [http-nio-8080-exec-8] ERROR com.ray.blog.handler.ControllerExceptionHandler - find exception:e=/ by zero 21:07:40.673 [http-nio-8080-exec-8] WARN org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Resolved [java.lang.ArithmeticException: / by zero]
|
页面:
具体异常信息以注释方式被注入:
显然通过@ExceptionHandler
的多重组合,我们可以应用于不同异常的拦截,分别设置重定向的页面
使用注解处理自定义异常
假设我们自定义异常NotFound:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.ray.blog;
import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException { public NotFoundException(){ } public NotFoundException(String message){ super(message); } public NotFoundException(String message, Throwable cause){ super(message, cause); } }
|
Controller类中特意加入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.ray.blog.web; import com.ray.blog.NotFoundException; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @GetMapping("/") public String index(){ String blog = null; if(blog == null){ throw new NotFoundException("博客不存在"); } return "index"; } }
|
我们在Handler类中加入额外两行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.ray.blog.handler;
import lombok.extern.log4j.Log4j2; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;
@ControllerAdvice @Log4j2 public class ControllerExceptionHandler { @ExceptionHandler(Exception.class) public ModelAndView exceptionHandler(HttpServletRequest httpServletRequest, Exception e) throws Exception { log.error("find exception:e={}",e.getMessage()); if(AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null){ throw e; } ModelAndView mv = new ModelAndView(); mv.addObject("url", httpServletRequest.getRequestURL()); mv.addObject("exception", e); mv.setViewName("error/error"); return mv; } }
|
随后我们对127.0.0.1:8080
进行访问,会抛出我们自定义的异常,并由Springboot直接跳转到静态的404页面。