복붙노트

[SPRING] Spring에서 필터에 던져진 예외를 관리하는 방법?

SPRING

Spring에서 필터에 던져진 예외를 관리하는 방법?

5xx 오류 코드를 관리하기 위해 일반적인 방법을 사용하고 싶습니다. db가 내 전체 봄 응용 프로그램에서 작동하지 않는 경우를 예로 들어 봅시다. 나는 스택 추적 대신에 꽤 오류 json을 원한다.

컨트롤러의 경우 다른 예외에 대한 @ControllerAdvice 클래스가 있으며 이는 요청의 중간에 db가 중지되는 경우를 잡는 중입니다. 그러나 이것이 전부는 아닙니다. OncePerRequestFilter를 확장하는 커스텀 CorsFilter가 있고 거기에서 doFilter를 호출 할 때 CannotGetJdbcConnectionException이 발생하고 @ControllerAdvice가 관리하지 않습니다. 온라인으로 여러 가지를 읽었을 때 혼란 스러웠습니다.

그래서 나는 많은 질문을 가지고있다 :

이 문제를 해결할 수있는 좋은 방법이 있습니까? 감사

해결법

  1. ==============================

    1.이것이 내가 한 일입니다.

    이것이 내가 한 일입니다.

    여기서 필터에 대한 기본 사항을 읽었을 때 필터 체인에서 첫 번째가 될 사용자 지정 필터를 만들어야한다는 것을 알았고 거기에서 발생할 수있는 모든 런타임 예외를 잡으려고 시도합니다. 그렇다면 json을 수동으로 생성하여 응답에 넣어야합니다.

    여기 내 맞춤 필터가 있습니다.

    public class ExceptionHandlerFilter extends OncePerRequestFilter {
    
        @Override
        public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            try {
                filterChain.doFilter(request, response);
            } catch (RuntimeException e) {
    
                // custom error response class used across my project
                ErrorResponse errorResponse = new ErrorResponse(e);
    
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                response.getWriter().write(convertObjectToJson(errorResponse));
        }
    }
    
        public String convertObjectToJson(Object object) throws JsonProcessingException {
            if (object == null) {
                return null;
            }
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(object);
        }
    }
    

    그리고 나서 CorsFilter 앞에 web.xml에 추가했습니다. 그리고 그것은 작동합니다!

    <filter> 
        <filter-name>exceptionHandlerFilter</filter-name> 
        <filter-class>xx.xxxxxx.xxxxx.api.controllers.filters.ExceptionHandlerFilter</filter-class> 
    </filter> 
    
    
    <filter-mapping> 
        <filter-name>exceptionHandlerFilter</filter-name> 
        <url-pattern>/*</url-pattern> 
    </filter-mapping> 
    
    <filter> 
        <filter-name>CorsFilter</filter-name> 
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
    </filter> 
    
    <filter-mapping>
        <filter-name>CorsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  2. ==============================

    2.나는이 문제를 직접 다루었으며 아래의 단계를 수행하여 등록 된 필터에서 Throw 된 예외에 대해 @ControllerAdvise로 주석 된 ExceptionController를 재사용했습니다.

    나는이 문제를 직접 다루었으며 아래의 단계를 수행하여 등록 된 필터에서 Throw 된 예외에 대해 @ControllerAdvise로 주석 된 ExceptionController를 재사용했습니다.

    분명히 예외를 처리 할 수있는 많은 방법이 있지만 내 경우에는 ExceptionController에서 예외를 처리하기를 원했습니다. 왜냐하면 저는 고집스럽고 동일한 코드를 복사 / 붙여 넣기를 원하지 않기 때문에 (즉, 일부 처리 / ExceptionController의 코드 로깅). 필자는 필터가 아닌 나머지 JSON 응답을 반환하고 싶습니다.

    {
      "status": 400,
      "message": "some exception thrown when executing the request"
    }
    

    어쨌든, 나는 ExceptionHandler를 사용하여 다음과 같이 약간의 추가 작업을해야했다.

    단계

    샘플 코드

    //sample Filter, to be added in web.xml
    public MyFilterThatThrowException implements Filter {
       //Spring Controller annotated with @ControllerAdvise which has handlers
       //for exceptions
       private MyExceptionController myExceptionController; 
    
       @Override
       public void destroy() {
            // TODO Auto-generated method stub
       }
    
       @Override
       public void init(FilterConfig arg0) throws ServletException {
           //Manually get an instance of MyExceptionController
           ApplicationContext ctx = WebApplicationContextUtils
                      .getRequiredWebApplicationContext(arg0.getServletContext());
    
           //MyExceptionHanlder is now accessible because I loaded it manually
           this.myExceptionController = ctx.getBean(MyExceptionController.class); 
       }
    
       @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse res = (HttpServletResponse) response;
    
            try {
               //code that throws exception
            } catch(Exception ex) {
              //MyObject is whatever the output of the below method
              MyObject errorDTO = myExceptionController.handleMyException(req, ex); 
    
              //set the response object
              res.setStatus(errorDTO .getStatus());
              res.setContentType("application/json");
    
              //pass down the actual obj that exception handler normally send
              ObjectMapper mapper = new ObjectMapper();
              PrintWriter out = res.getWriter(); 
              out.print(mapper.writeValueAsString(errorDTO ));
              out.flush();
    
              return; 
            }
    
            //proceed normally otherwise
            chain.doFilter(request, response); 
         }
    }
    

    그리고 이제 정상적인 경우 예외를 처리하는 샘플 Spring Controller (즉, 필터 수준에서 일반적으로 throw되지 않는 예외, 즉 필터에서 throw 된 예외에 대해 사용하려는 예외)

    //sample SpringController 
    @ControllerAdvice
    public class ExceptionController extends ResponseEntityExceptionHandler {
    
        //sample handler
        @ResponseStatus(value = HttpStatus.BAD_REQUEST)
        @ExceptionHandler(SQLException.class)
        public @ResponseBody MyObject handleSQLException(HttpServletRequest request,
                Exception ex){
            ErrorDTO response = new ErrorDTO (400, "some exception thrown when "
                    + "executing the request."); 
            return response;
        }
        //other handlers
    }
    

    Filter에서 Throw 된 Exception에 ExceptionController를 사용하고자하는 사람들과 솔루션 공유.

  3. ==============================

    3.일반적인 방법을 원할 경우 web.xml에 오류 페이지를 정의 할 수 있습니다.

    일반적인 방법을 원할 경우 web.xml에 오류 페이지를 정의 할 수 있습니다.

    <error-page>
      <exception-type>java.lang.Throwable</exception-type>
      <location>/500</location>
    </error-page>
    

    그리고 Spring MVC에서 매핑을 추가하십시오 :

    @Controller
    public class ErrorController {
    
        @RequestMapping(value="/500")
        public @ResponseBody String handleException(HttpServletRequest req) {
            // you can get the exception thrown
            Throwable t = (Throwable)req.getAttribute("javax.servlet.error.exception");
    
            // customize response to what you want
            return "Internal server error.";
        }
    }
    
  4. ==============================

    4.이것은 기본 스프링 부트 / 에러 핸들러를 오버라이드하여 내 솔루션입니다.

    이것은 기본 스프링 부트 / 에러 핸들러를 오버라이드하여 내 솔루션입니다.

    package com.mypackage;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.web.ErrorAttributes;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.util.Assert;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Map;
    
    /**
     * This controller is vital in order to handle exceptions thrown in Filters.
     */
    @RestController
    @RequestMapping("/error")
    public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController {
    
        private final static Logger LOGGER = LoggerFactory.getLogger(ErrorController.class);
    
        private final ErrorAttributes errorAttributes;
    
        @Autowired
        public ErrorController(ErrorAttributes errorAttributes) {
            Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
            this.errorAttributes = errorAttributes;
        }
    
        @Override
        public String getErrorPath() {
            return "/error";
        }
    
        @RequestMapping
        public ResponseEntity<Map<String, Object>> error(HttpServletRequest aRequest, HttpServletResponse response) {
            RequestAttributes requestAttributes = new ServletRequestAttributes(aRequest);
            Map<String, Object> result =     this.errorAttributes.getErrorAttributes(requestAttributes, false);
    
            Throwable error = this.errorAttributes.getError(requestAttributes);
    
            ResponseStatus annotation =     AnnotationUtils.getAnnotation(error.getClass(), ResponseStatus.class);
            HttpStatus statusCode = annotation != null ? annotation.value() : HttpStatus.INTERNAL_SERVER_ERROR;
    
            result.put("status", statusCode.value());
            result.put("error", statusCode.getReasonPhrase());
    
            LOGGER.error(result.toString());
            return new ResponseEntity<>(result, statusCode) ;
        }
    
    }
    
  5. ==============================

    5.응용 프로그램의 상태를 테스트하고 문제가 발생하면 HTTP 오류를 반환 할 때 필터를 제안합니다. 아래 필터는 모든 HTTP 요청을 처리합니다. javax 필터가있는 Spring Boot에서 가장 짧은 솔루션.

    응용 프로그램의 상태를 테스트하고 문제가 발생하면 HTTP 오류를 반환 할 때 필터를 제안합니다. 아래 필터는 모든 HTTP 요청을 처리합니다. javax 필터가있는 Spring Boot에서 가장 짧은 솔루션.

    구현에서 다양한 조건이 될 수 있습니다. 내 경우에는 applicationManager가 응용 프로그램이 준비되었는지 테스트합니다.

    import ...ApplicationManager;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Component
    public class SystemIsReadyFilter implements Filter {
    
        @Autowired
        private ApplicationManager applicationManager;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {}
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            if (!applicationManager.isApplicationReady()) {
                ((HttpServletResponse) response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "The service is booting.");
            } else {
                chain.doFilter(request, response);
            }
        }
    
        @Override
        public void destroy() {}
    }
    
  6. ==============================

    6.그래서, 위의 답변이 합쳐진 것을 기반으로 한 작업이 있습니다. 이미 @ControllerAdvice로 주석 된 GlobalExceptionHandler가 있었으며 필터에서 나온 예외를 처리하기 위해 해당 코드를 다시 사용할 수있는 방법을 찾고 싶었습니다.

    그래서, 위의 답변이 합쳐진 것을 기반으로 한 작업이 있습니다. 이미 @ControllerAdvice로 주석 된 GlobalExceptionHandler가 있었으며 필터에서 나온 예외를 처리하기 위해 해당 코드를 다시 사용할 수있는 방법을 찾고 싶었습니다.

    가장 간단한 해결책은 예외 처리기 만 남겨두고 다음과 같이 오류 컨트롤러를 구현하는 것입니다.

    @Controller
    public class ErrorControllerImpl implements ErrorController {
      @RequestMapping("/error")
      public void handleError(HttpServletRequest request) throws Throwable {
        if (request.getAttribute("javax.servlet.error.exception") != null) {
          throw (Throwable) request.getAttribute("javax.servlet.error.exception");
        }
      }
    }
    

    따라서 예외로 인해 발생한 오류는 먼저 ErrorController를 통과하고 @Controller 컨텍스트 내에서 예외를 다시 처리하여 예외 처리기로 리디렉션됩니다 (다른 오류 (예외에 의해 직접적으로 발생하지 않음)는 수정하지 않고 ErrorController를 통과합니다 .

    이것이 실제로 나쁜 생각 인 이유는 무엇입니까?

  7. ==============================

    7.@ControllerAdvice가 작동해야하기 때문에 이상합니다. 올바른 Exception을 catch하고 있습니까?

    @ControllerAdvice가 작동해야하기 때문에 이상합니다. 올바른 Exception을 catch하고 있습니까?

    @ControllerAdvice
    public class GlobalDefaultExceptionHandler {
    
        @ResponseBody
        @ExceptionHandler(value = DataAccessException.class)
        public String defaultErrorHandler(HttpServletResponse response, DataAccessException e) throws Exception {
           response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
           //Json return
        }
    }
    

    또한 CorsFilter에서이 예외를 catch하고 500 오류를 보내려고합니다.

    @ExceptionHandler(DataAccessException.class)
    @ResponseBody
    public String handleDataException(DataAccessException ex, HttpServletResponse response) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        //Json return
    }
    
  8. from https://stackoverflow.com/questions/34595605/how-to-manage-exceptions-thrown-in-filters-in-spring by cc-by-sa and MIT license