Spring Boot全局异常处理

淡淡的烟草味﹌ 2021-11-04 16:32 460阅读 0赞

Spring Boot默认启动的时候会注入一个专门处理异常的自动配置类:

  1. @Configuration
  2. @ConditionalOnWebApplication(type = Type.SERVLET)
  3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
  4. // Load before the main WebMvcAutoConfiguration so that the error View is available
  5. @AutoConfigureBefore(WebMvcAutoConfiguration.class)
  6. @EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
  7. public class ErrorMvcAutoConfiguration {
  8. private final ServerProperties serverProperties;
  9. private final List<ErrorViewResolver> errorViewResolvers;
  10. public ErrorMvcAutoConfiguration(ServerProperties serverProperties,
  11. ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
  12. this.serverProperties = serverProperties;
  13. this.errorViewResolvers = errorViewResolversProvider.getIfAvailable();
  14. }
  15. @Bean
  16. @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
  17. public DefaultErrorAttributes errorAttributes() {
  18. return new DefaultErrorAttributes(
  19. this.serverProperties.getError().isIncludeException());
  20. }
  21. @Bean
  22. @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
  23. public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
  24. return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
  25. this.errorViewResolvers);
  26. }
  27. //.................
  28. }

查看源码发现其会注入一个默认的BasicErrorController来处理异常,所以主要看这个Controller:

  1. @Controller
  2. @RequestMapping("${server.error.path:${error.path:/error}}")
  3. public class BasicErrorController extends AbstractErrorController {
  4. @RequestMapping(produces = "text/html")
  5. public ModelAndView errorHtml(HttpServletRequest request,
  6. HttpServletResponse response) {
  7. HttpStatus status = getStatus(request);
  8. Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
  9. request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  10. response.setStatus(status.value());
  11. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  12. return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
  13. }
  14. @RequestMapping
  15. @ResponseBody
  16. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  17. Map<String, Object> body = getErrorAttributes(request,
  18. isIncludeStackTrace(request, MediaType.ALL));
  19. HttpStatus status = getStatus(request);
  20. return new ResponseEntity<>(body, status);
  21. }
  22. }

这里根据返回类型不同分为了两个方法,errorHtml方法返回ModelAndView,error方法返回ResponseEntity,熟悉SpringMvc的同学应该对这两个类不陌生。其中errorHtml方法中的resolveErrorView方法由父类实现:

  1. public abstract class AbstractErrorController implements ErrorController {
  2. private final ErrorAttributes errorAttributes;
  3. private final List<ErrorViewResolver> errorViewResolvers;
  4. protected ModelAndView resolveErrorView(HttpServletRequest request,
  5. HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
  6. for (ErrorViewResolver resolver : this.errorViewResolvers) {
  7. ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
  8. if (modelAndView != null) {
  9. return modelAndView;
  10. }
  11. }
  12. return null;
  13. }
  14. }

这里具体是由ErrorViewResolver去处理的,这里当SpringBoot启动的时候会自动装载一个默认的ErrorViewResolver进来,其还实现了Ordered接口,优先级最低,如下:

  1. public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
  2. private static final Map<Series, String> SERIES_VIEWS;
  3. static {
  4. Map<Series, String> views = new EnumMap<>(Series.class);
  5. views.put(Series.CLIENT_ERROR, "4xx");
  6. views.put(Series.SERVER_ERROR, "5xx");
  7. SERIES_VIEWS = Collections.unmodifiableMap(views);
  8. }
  9. private ApplicationContext applicationContext;
  10. private final ResourceProperties resourceProperties;
  11. private final TemplateAvailabilityProviders templateAvailabilityProviders;
  12. private int order = Ordered.LOWEST_PRECEDENCE;
  13. @Override
  14. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
  15. Map<String, Object> model) {
  16. ModelAndView modelAndView = resolve(String.valueOf(status), model);
  17. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  18. modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
  19. }
  20. return modelAndView;
  21. }
  22. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  23. String errorViewName = "error/" + viewName;
  24. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
  25. .getProvider(errorViewName, this.applicationContext);
  26. if (provider != null) {
  27. return new ModelAndView(errorViewName, model);
  28. }
  29. return resolveResource(errorViewName, model);
  30. }
  31. private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
  32. for (String location : this.resourceProperties.getStaticLocations()) {
  33. try {
  34. Resource resource = this.applicationContext.getResource(location);
  35. resource = resource.createRelative(viewName + ".html");
  36. if (resource.exists()) {
  37. return new ModelAndView(new HtmlResourceView(resource), model);
  38. }
  39. }
  40. catch (Exception ex) {
  41. }
  42. }
  43. return null;
  44. }
  45. }

ResourceProperties中包含了默认的四个静态资源路径:

  1. @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
  2. public class ResourceProperties {
  3. private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
  4. "classpath:/META-INF/resources/", "classpath:/resources/",
  5. "classpath:/static/", "classpath:/public/" };
  6. /**
  7. * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
  8. * /resources/, /static/, /public/].
  9. */
  10. private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
  11. }

所以分析源码可以发现,SpringBoot会依次从”classpath:/META-INF/resources/“, “classpath:/resources/“,”classpath:/static/“, “classpath:/public/“这四个目录下面寻找是否存在error目录,然后根据错误码errorCode去寻找具体的errorCode.html页面,如果找到则构造成ModelAndView对象返回,我们可以通过添加自定义ErrorViewResolver可以构造自己的异常目录:

  1. @Component
  2. public class CustomErrorViewResolver implements ErrorViewResolver,Ordered,ApplicationContextAware {
  3. private final String CUSTOM_RESOURCE_LOCATION = "classpath:/static/custom_error/";
  4. private ApplicationContext applicationContext;
  5. @Override
  6. public int getOrder() {
  7. return 0;
  8. }
  9. /**
  10. * 返回null的时候会由后续的ErrorViewResolver去解析
  11. * @see org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController#resolveErrorView(HttpServletRequest, HttpServletResponse, HttpStatus, Map)
  12. * @param request
  13. * @param status
  14. * @param model
  15. * @return
  16. */
  17. @Override
  18. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  19. if(status.equals(HttpStatus.INTERNAL_SERVER_ERROR)){
  20. try {
  21. Resource resource = this.applicationContext.getResource(CUSTOM_RESOURCE_LOCATION);
  22. resource = resource.createRelative(String.valueOf(status) + ".html");
  23. if (resource.exists()) {
  24. return new ModelAndView(new HtmlResourceView(resource), model);
  25. }
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. return null;
  31. }
  32. @Override
  33. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  34. this.applicationContext = applicationContext;
  35. }
  36. class HtmlResourceView implements View {
  37. private Resource resource;
  38. HtmlResourceView(Resource resource) {
  39. this.resource = resource;
  40. }
  41. @Override
  42. public String getContentType() {
  43. return MediaType.TEXT_HTML_VALUE;
  44. }
  45. @Override
  46. public void render(Map<String, ?> model, HttpServletRequest request,
  47. HttpServletResponse response) throws Exception {
  48. response.setContentType(getContentType());
  49. FileCopyUtils.copy(this.resource.getInputStream(),
  50. response.getOutputStream());
  51. }
  52. }
  53. }

还可以使用常规的Spring MVC特性,比如@ExceptionHandler方法和@ControllerAdvice解析需要处理的异常。其他未处理的异常由ErrorController来解析。

  1. @ControllerAdvice
  2. public class CustomControllerAdvice extends ResponseEntityExceptionHandler {
  3. @ExceptionHandler(CustomException.class)
  4. @ResponseBody
  5. ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
  6. HttpStatus status = getStatus(request);
  7. Map<String, Object> map = new HashMap<>();
  8. map.put("status",status.value());
  9. map.put("msg",ex.getMessage());
  10. return new ResponseEntity<>(map, status);
  11. }
  12. private HttpStatus getStatus(HttpServletRequest request) {
  13. Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
  14. if (statusCode == null) {
  15. return HttpStatus.INTERNAL_SERVER_ERROR;
  16. }
  17. return HttpStatus.valueOf(statusCode);
  18. }
  19. }

官方文档:Spring Boot Error Handling

发表评论

表情:
评论列表 (有 0 条评论,460人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring Boot全局异常处理

    在开发Web应用程序时,异常处理是非常重要的。在Spring Boot中,我们可以使用全局异常处理器来捕获并处理应用程序中的异常。本文将介绍如何使用Spring Boot的全局