1. 程式人生 > >SpringBoot初始教程之統一異常處理(三)

SpringBoot初始教程之統一異常處理(三)

SpringBoot初始教程之統一異常處理(三)

1.介紹

在日常開發中發生了異常,往往是需要通過一個統一的異常處理處理所有異常,來保證客戶端能夠收到友好的提示。SpringBoot在頁面 
發生異常的時候會自動把請求轉到/error,SpringBoot內建了一個BasicErrorController對異常進行統一的處理,當然也可以自定義這個路徑

application.yaml


server:
  port: 8080
  error:
    path: /custom/error
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

BasicErrorController

提供兩種返回錯誤一種是頁面返回、當你是頁面請求的時候就會返回頁面,另外一種是json請求的時候就會返回json錯誤


    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(body, status);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

分別會有如下兩種返回

Renderings


{
  "timestamp": 1478571808052,
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/rpc"
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.通用Exception處理

通過使用@ControllerAdvice來進行統一異常處理,@ExceptionHandler(value = Exception.class)來指定捕獲的異常 
下面針對兩種異常進行了特殊處理分別返回頁面和json資料,使用這種方式有個侷限,無法根據不同的頭部返回不同的資料格式,而且無法針對404、403等多種狀態進行處理


    @ControllerAdvice
    public class GlobalExceptionHandler {
        public static final String DEFAULT_ERROR_VIEW = "error";
        @ExceptionHandler(value = CustomException.class)
        @ResponseBody
        public ResponseEntity defaultErrorHandler(HttpServletRequest req, CustomException e) throws Exception {
            return ResponseEntity.ok("ok");
        }
        @ExceptionHandler(value = Exception.class)
        public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
            ModelAndView mav = new ModelAndView();
            mav.addObject("exception", e);
            mav.addObject("url", req.getRequestURL());
            mav.setViewName(DEFAULT_ERROR_VIEW);
            return mav;
        }
    }


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3.自定義BasicErrorController 錯誤處理

在初始介紹哪裡提到了BasicErrorController,這個是SpringBoot的預設錯誤處理,也是一種全域性處理方式。咱們可以模仿這種處理方式自定義自己的全域性錯誤處理 
下面定義了一個自己的BasicErrorController,可以根據自己的需求自定義errorHtml()error()的返回值。


    @Controller
    @RequestMapping("${server.error.path:${error.path:/error}}")
    public class BasicErrorController extends AbstractErrorController {
        private final ErrorProperties errorProperties;
        private static final Logger LOGGER = LoggerFactory.getLogger(BasicErrorController.class);
        @Autowired
        private ApplicationContext applicationContext;

        /**
         * Create a new {@link org.springframework.boot.autoconfigure.web.BasicErrorController} instance.
         *
         * @param errorAttributes the error attributes
         * @param errorProperties configuration properties
         */
        public BasicErrorController(ErrorAttributes errorAttributes,
                                    ErrorProperties errorProperties) {
            this(errorAttributes, errorProperties,
                    Collections.<ErrorViewResolver>emptyList());
        }

        /**
         * Create a new {@link org.springframework.boot.autoconfigure.web.BasicErrorController} instance.
         *
         * @param errorAttributes    the error attributes
         * @param errorProperties    configuration properties
         * @param errorViewResolvers error view resolvers
         */
        public BasicErrorController(ErrorAttributes errorAttributes,
                                    ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
            super(errorAttributes, errorViewResolvers);
            Assert.notNull(errorProperties, "ErrorProperties must not be null");
            this.errorProperties = errorProperties;
        }

        @Override
        public String getErrorPath() {
            return this.errorProperties.getPath();
        }

        @RequestMapping(produces = "text/html")
        public ModelAndView errorHtml(HttpServletRequest request,
                                      HttpServletResponse response) {
            HttpStatus status = getStatus(request);
            Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                    request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
            response.setStatus(status.value());
            ModelAndView modelAndView = resolveErrorView(request, response, status, model);
            insertError(request);
            return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
        }



        @RequestMapping
        @ResponseBody
        public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
            Map<String, Object> body = getErrorAttributes(request,
                    isIncludeStackTrace(request, MediaType.ALL));
            HttpStatus status = getStatus(request);
            insertError(request);
            return new ResponseEntity(body, status);
        }

        /**
         * Determine if the stacktrace attribute should be included.
         *
         * @param request  the source request
         * @param produces the media type produced (or {@code MediaType.ALL})
         * @return if the stacktrace attribute should be included
         */
        protected boolean isIncludeStackTrace(HttpServletRequest request,
                                              MediaType produces) {
            ErrorProperties.IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
            if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
                return true;
            }
            if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
                return getTraceParameter(request);
            }
            return false;
        }

        /**
         * Provide access to the error properties.
         *
         * @return the error properties
         */
        protected ErrorProperties getErrorProperties() {
            return this.errorProperties;
        }
    }

  • 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
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

SpringBoot提供了一種特殊的Bean定義方式,可以讓我們容易的覆蓋已經定義好的Controller,原生的BasicErrorController是定義在ErrorMvcAutoConfiguration中的 
具體程式碼如下:


    @Bean
    @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                this.errorViewResolvers);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以看到這個註解@ConditionalOnMissingBean 意思就是定義這個bean 當 ErrorController.class 這個沒有定義的時候, 
意思就是說只要我們在程式碼裡面定義了自己的ErrorController.class時,這段程式碼就不生效了,具體自定義如下:


    @Configuration
    @ConditionalOnWebApplication
    @ConditionalOnClass({Servlet.class, DispatcherServlet.class})
    @AutoConfigureBefore(WebMvcAutoConfiguration.class)
    @EnableConfigurationProperties(ResourceProperties.class)
    public class ConfigSpringboot {
        @Autowired(required = false)
        private List<ErrorViewResolver> errorViewResolvers;
        private final ServerProperties serverProperties;

        public ConfigSpringboot(
                ServerProperties serverProperties) {
            this.serverProperties = serverProperties;
        }

        @Bean
        public MyBasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
            return new MyBasicErrorController(errorAttributes, this.serverProperties.getError(),
                    this.errorViewResolvers);
        }


    }

  • 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

在使用的時候需要注意MyBasicErrorController不能被自定義掃描Controller掃描到,否則無法啟動。

3.總結

一般來說自定義BasicErrorController這種方式比較實用,因為可以通過不同的頭部返回不同的資料格式,在配置上稍微複雜一些,但是從實用的角度來說比較方便而且可以定義通用元件