java-如何管理Spring中过滤器中引发的异常?

我想使用通用方式来管理5xx错误代码,特别是当整个Spring应用程序中的db关闭时。 我想要一个漂亮的错误json而不是堆栈跟踪。

对于控制器,我有一个@ControllerAdvice类用于不同的异常,这也捕获了db在请求中间停止的情况。 但这并不是全部。 我也碰巧有一个自定义HandlerExceptionResolver,扩展了HandlerExceptionResolver,在那里,当我致电AccessDeniedException时,我得到了CannotGetJdbcConnectionException,它将不会受到@ControllerAdvice的管理。我在网上阅读了几件事,这只会让我更加困惑。

所以我有很多问题:

  • 我是否需要实施自定义过滤器? 我找到了HandlerExceptionResolver,但这只能处理HandlerExceptionResolverAccessDeniedException
  • 我考虑实现自己的HandlerExceptionResolver,但这使我感到怀疑,我没有任何自定义异常要管理,必须有比这更明显的方法。 我还尝试添加一个try / catch并调用HandlerExceptionResolver的实现(应该足够好,我的例外没有什么特别的),但这在响应中不返回任何内容,我得到一个状态200和一个空主体。

有什么好办法解决这个问题吗? 谢谢

kopelitsa asked 2019-10-09T14:40:35Z
9个解决方案
52 votes

这就是我所做的:

我在这里阅读了有关过滤器的基础知识,并且发现需要创建一个自定义过滤器,该过滤器将首先出现在过滤器链中,并且将尝试捕获所有可能在此发生的运行时异常。 然后,我需要手动创建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>
kopelitsa answered 2019-10-09T14:41:17Z
12 votes

我自己遇到了此问题,我执行了以下步骤来重复使用我的@ControllerAdvise(其注释为@ControllerAdvise),以将Exceptions扔到注册的过滤器中。

显然有很多处理异常的方法,但就我而言,我希望由我的ExceptionController处理该异常,因为我很固执,也因为我不想复制/粘贴相同的代码(即,我有一些处理/ Exceptions中的日志记录代码)。 我想返回优美的JSON响应,就像不是从过滤器抛出的其余异常一样。

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

无论如何,我设法利用了ExceptionController,并且不得不做一些额外的操作,如下所示:

脚步


  1. 您有一个自定义过滤器,该过滤器可能会也可能不会引发异常
  2. 您有一个使用ExceptionController(即MyExceptionController)处理异常的Spring控制器

示例代码

//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); 
     }
}

现在是在正常情况下处理ExceptionController的示例Spring控制器(即通常不在Filter级别引发的异常,我们要用于在Filter中引发的异常)

//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
}

与希望将ExceptionController用于过滤器中的Exceptions的用户共享解决方案。

Raf answered 2019-10-09T14:42:52Z
9 votes

如果需要通用方法,可以在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.";
    }
}
holmis83 answered 2019-10-09T14:43:27Z
7 votes

因此,这是基于上述答案的综合...我已经在ErrorController中添加了@Controller注释,并且我还想找到一种方法来重用该代码来处理来自过滤器的异常。

我能找到的最简单的解决方案是不理会异常处理程序,并按如下方式实现错误控制器:

@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

有什么理由为什么这实际上是个坏主意?

AndyB answered 2019-10-09T14:44:31Z
5 votes

这是我通过覆盖默认的Spring Boot / error处理程序的解决方案

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) ;
    }

}
walv answered 2019-10-09T14:45:13Z
4 votes

当您要测试应用程序的状态时,如果出现问题,请返回HTTP错误,我建议您使用过滤器。 下面的过滤器处理所有HTTP请求。 Spring Boot中带有javax过滤器的最短解决方案。

在执行中可以有各种条件。 在我的情况下,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() {}
}
Cyva answered 2019-10-09T14:45:54Z
1 votes

这很奇怪,因为@ControllerAdvice应该可以工作,您是否捕获了正确的Exception?

@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中捕获此异常并发送500错误,类似这样

@ExceptionHandler(DataAccessException.class)
@ResponseBody
public String handleDataException(DataAccessException ex, HttpServletResponse response) {
    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    //Json return
}
user2669657 answered 2019-10-09T14:46:32Z
1 votes

只是为了补充提供的其他好的答案,我最近也想在一个简单的SpringBoot应用程序中使用一个错误/异常处理组件,该组件包含可能引发异常的过滤器,以及可能从控制器方法引发的其他异常。

幸运的是,似乎没有什么可以阻止您将控制器建议与Spring默认错误处理程序的替代组合以提供一致的响应有效负载,允许您共享逻辑,检查来自过滤器的异常,捕获特定的服务引发的异常等。

例如。


@ControllerAdvice
@RestController
public class GlobalErrorHandler implements ErrorController {

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(ValidationException.class)
  public Error handleValidationException(
      final ValidationException validationException) {
    return new Error("400", "Incorrect params"); // whatever
  }

  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(Exception.class)
  public Error handleUnknownException(final Exception exception) {
    return new Error("500", "Unexpected error processing request");
  }

  @RequestMapping("/error")
  public ResponseEntity handleError(final HttpServletRequest request,
      final HttpServletResponse response) {

    Object exception = request.getAttribute("javax.servlet.error.exception");

    // TODO: Logic to inspect exception thrown from Filters...
    return ResponseEntity.badRequest().body(new Error(/* whatever */));
  }

  @Override
  public String getErrorPath() {
    return "/error";
  }

}
Tom Bunting answered 2019-10-09T14:47:20Z
0 votes

我想根据@kopelitsa的答案提供解决方案。 主要区别在于:

  1. 通过使用LogoutFilter重用控制器异常处理。
  2. 在XML配置上使用Java配置

首先,您需要确保您有一个类来处理在常规RestController / Controller中发生的异常(用LogoutFilter@ControllerAdvice注释的类以及用@ExceptionHandler注释的方法)。 这可以处理您在控制器中发生的异常。 这是使用RestControllerAdvice的示例:

@RestControllerAdvice
public class ExceptionTranslator {

    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorDTO processRuntimeException(RuntimeException e) {
        return createErrorDTO(HttpStatus.INTERNAL_SERVER_ERROR, "An internal server error occurred.", e);
    }

    private ErrorDTO createErrorDTO(HttpStatus status, String message, Exception e) {
        (...)
    }
}

要在Spring Security过滤器链中重用此行为,您需要定义一个Filter并将其挂钩到您的安全配置中。 筛选器需要将异常重定向到上面定义的异常处理。 这是一个例子:

@Component
public class FilterChainExceptionHandler extends OncePerRequestFilter {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        try {
            filterChain.doFilter(request, response);
        } catch (RuntimeException e) {
            log.error("Spring Security Filter Chain RuntimeException:", e);
            resolver.resolveException(request, response, null, e);
        }
    }
}

然后需要将创建的过滤器添加到SecurityConfiguration。 您需要非常早地将其挂接到链中,因为不会捕获所有先前过滤器的异常。 就我而言,将其添加到LogoutFilter之前是合理的。请参阅默认文档链及其官方文档中的顺序。 这是一个例子:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private FilterChainExceptionHandler filterChainExceptionHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
            (...)
    }

}
ssc-hrep3 answered 2019-10-09T14:48:34Z
translate from https://stackoverflow.com:/questions/34595605/how-to-manage-exceptions-thrown-in-filters-in-spring