スプリングboot異常処理メカニズムについて


プロジェクトではグローバルな異常処理が必要であり,ユーザにとって体験がより友好的であり,システムにとって異常情報を遡り,異常の出所を見つけることができる.Springbootでは異常をどのように処理しますか?
Springに異常を処理するインタフェースがあります
public interface HandlerExceptionResolver {
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

}

このインタフェースを実装し、springコンテナに登録し、controllerで異常が発生した場合にインタフェースを呼び出して処理します.値指定ビューを返すと自動的に指定ビューにジャンプし、nullを返すと次の異常プロセッサを呼び出して実行します.Springbootでは以下のようにコンテナに登録できます
    @Bean
    public ApplicationHandlerExceptionResolver handlerExceptionResolver(){
        return new ApplicationHandlerExceptionResolver();
    }

または
@EnableWebMvc
@EnableAsync
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureHandlerExceptionResolvers(List exceptionResolvers) {
        super.configureHandlerExceptionResolvers(exceptionResolvers);
        exceptionResolvers.add(new ApplicationHandlerExceptionResolver());
    }
}

Springでは、@Controller Adviceおよび@ExceptionHandler(ApplicationException.class)注記で指定した例外をグローバルに処理することもできます.
@Controller
@ControllerAdvice
@RequestMapping("/error/")
public class GlobalExceptionController{

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionController.class);

    @ExceptionHandler(ApplicationException.class)
    public Result handlerException(Exception e) {
        LOGGER.error("         ");
        return Result.serverError();
    }
}

この2つを同時に上のように配置すると、2つ目の処理が機能しないことに気づきます.これはなぜですか.ソースコードを見ると、WebMvcConfigurationSupportクラスにこのような方法があることがわかります.
    @Bean
    public HandlerExceptionResolver handlerExceptionResolver() {
        List exceptionResolvers = new ArrayList();
        configureHandlerExceptionResolvers(exceptionResolvers);
        if (exceptionResolvers.isEmpty()) {
            addDefaultHandlerExceptionResolvers(exceptionResolvers);
        }
        extendHandlerExceptionResolvers(exceptionResolvers);
        HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
        composite.setOrder(0);
        composite.setExceptionResolvers(exceptionResolvers);
        return composite;
    }

上記の方法の中のc o n f i g u r e HandlerExceptionResolvers(exceptionResolvers)に注意してください.この方法の実現はちょうど私たちがカバーしているもので、もし私たちが異常プロセッサを追加したら、デフォルトの異常プロセッサを追加することはありません.第2の異常処理の方法はちょうどデフォルトの異常プロセッサによって完成したので、どのようにして両者を有効にすることができますか?この行extendHandlerExceptionResolvers(exceptionResolvers)を見続けます.この方法は登録した例外プロセッサとデフォルトのプロセッサを統合するので、この方法を書き換える必要があります.
    @Override
    public void extendHandlerExceptionResolvers(List exceptionResolvers) {
        super.extendHandlerExceptionResolvers(exceptionResolvers);
        exceptionResolvers.add(new ApplicationHandlerExceptionResolver());
    }

これにより、デフォルトの例外解析器とカスタム解析器を登録できますが、HandlerExceptionResolverインタフェースを実装する例外プロセッサは有効ではありません.これはなぜですか?なぜなら、例外レジストラは順番に実行され、現在返されているModelAndViewオブジェクトがnullでない場合、後続の例外プロセッサの実行が中断され、
    @ExceptionHandler(ApplicationException.class)
    public Result handlerException(Exception e) {
        LOGGER.error("         ");
        return Result.serverError();
    }

実行が完了するとModelAndViewオブジェクトが返されるので、自分たちが登録した例外解析器は実行されません.有効にするには、登録時に登録順序を調整するだけです.
    @Override
    public void extendHandlerExceptionResolvers(List exceptionResolvers) {
        super.extendHandlerExceptionResolvers(exceptionResolvers);
        exceptionResolvers.add(0, new ApplicationHandlerExceptionResolver());
    }

カスタマイズした例外解析器をトップに登録すればいいです.Springでの例外処理の仕方は大体そうですが、この2つの方法ではControllerレイヤ内の例外しか処理できません.レンダーレイヤやその他の異常にはどうしようもありません.グローバルな例外処理をするにはコンテナを組み合わせて処理する必要があります.
tomcatを例にとると、コンテナはリクエストを処理する際に例外が発生し、まずエラーページを探します.エラーページが構成されていない場合、HttpStatusはデフォルトで/errorパスに転送されます.したがって、/errorリクエストを処理するためにcontrollerクラスを書くことができます.springbootでは/errorリクエストを処理するcontrollerクラスが黙認されています.
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    private final ErrorProperties errorProperties;

    public BasicErrorController(ErrorAttributes errorAttributes,
            ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties,
                Collections.emptyList());
    }
    public BasicErrorController(ErrorAttributes errorAttributes,
            ErrorProperties errorProperties, List 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 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> error(HttpServletRequest request) {
        Map body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity>(body, status);
    }
    protected boolean isIncludeStackTrace(HttpServletRequest request,
            MediaType produces) {
        IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }
    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }

}

コントロールを書いてBasicerrorControllerを継承してカスタム処理の目的を達成することができます
@ControllerAdvice
@RequestMapping("/error/")
public class GlobalExceptionController extends BasicErrorController {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionController.class);

    private ErrorAttributes errorAttributes;

    public GlobalExceptionController(ErrorAttributes errorAttributes) {
        super(errorAttributes, new ErrorProperties());
        this.errorAttributes = errorAttributes;
    }

    @RequestMapping(value = SystemConstant.ERROR_NO_AUTH_PATH)
    public Result noAuth() {
        return Result.noAuth();
    }

    @RequestMapping(value = SystemConstant.URL_NO_EXISTS)
    public Result noExists() {
        return Result.urlNoExists();
    }

    @RequestMapping(value = SystemConstant.SERVER_ERROR)
    public Result serverError() {
        return Result.serverError();
    }

    @ExceptionHandler(ApplicationException.class)
    public Result handlerException(Exception e) {
        LOGGER.error("         ");
        return Result.serverError();
    }
}

tomcatでエラーコードと転送パスを構成して、例外を集中的に処理することもできます.
    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
        return new EmbeddedServletContainerCustomizer() {
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"));
                container.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/error/400"));
                container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/400"));
                container.addErrorPages(new ErrorPage(HttpStatus.BAD_GATEWAY, "/error/500"));
                container.addErrorPages(new ErrorPage(Throwable.class, "/error/500"));
                container.setDisplayName("ABC");
                container.setPort(80);
            }
        };
    }

以上がspringbootにおける異常処理方式である