Java 如何管理 Spring 过滤器中抛出的异常?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34595605/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 15:49:39  来源:igfitidea点击:

How to manage exceptions thrown in filters in Spring?

javaspringspring-security

提问by kopelitsa

I want to use generic way to manage 5xx error codes, let's say specifically the case when the db is down across my whole spring application. I want a pretty error json instead of a stack trace.

我想使用通用方法来管理 5xx 错误代码,让我们具体说一下整个 spring 应用程序中数据库关闭的情况。我想要一个漂亮的错误 json 而不是堆栈跟踪。

For the controllers I have a @ControllerAdviceclass for the different exceptions and this is also catching the case that the db is stopping in the middle of the request. But this is not all. I also happen to have a custom CorsFilterextending OncePerRequestFilterand there when i call doFilteri get the CannotGetJdbcConnectionExceptionand it will not be managed by the @ControllerAdvice. I read several things online that only made me more confused.

对于控制器,我有一个@ControllerAdvice针对不同异常的类,这也捕获了数据库在请求中间停止的情况。但这并不是全部。我也碰巧有一个自定义CorsFilter扩展OncePerRequestFilter,当我打电话时,doFilter我得到了CannotGetJdbcConnectionException,它不会由@ControllerAdvice. 我在网上读了几件事,这让我更加困惑。

So I have a lot of questions:

所以我有很多问题:

  • Do i need to implement a custom filter? I found the ExceptionTranslationFilterbut this only handles AuthenticationExceptionor AccessDeniedException.
  • I thought of implementing my own HandlerExceptionResolver, but this made me doubt, I don't have any custom exception to manage, there must be a more obvious way than this. I also tried to add a try/catch and call an implementation of the HandlerExceptionResolver(should be good enough, my exception is nothing special) but this is not returning anything in the response, i get a status 200 and an empty body.
  • 我需要实现自定义过滤器吗?我找到了,ExceptionTranslationFilter但这只能处理AuthenticationExceptionor AccessDeniedException
  • 我想实现我自己的HandlerExceptionResolver,但这让我怀疑,我没有任何自定义异常要管理,一定有比这更明显的方法。我还尝试添加一个 try/catch 并调用一个实现HandlerExceptionResolver(应该足够好,我的异常没什么特别的)但这在响应中没有返回任何内容,我得到一个状态 200 和一个空的正文。

Is there any good way to deal with this? Thanks

有没有什么好的办法来处理这个问题?谢谢

采纳答案by kopelitsa

So this is what I did:

所以这就是我所做的:

I read the basics about filters hereand I figured out that I need to create a custom filter that will be first in the filter chain and will have a try catch to catch all runtime exceptions that might occur there. Then i need to create the json manually and put it in the response.

我在这里阅读了有关过滤器的基础知识,我发现我需要创建一个自定义过滤器,该过滤器将位于过滤器链的第一个位置,并且将使用 try catch 来捕获可能在那里发生的所有运行时异常。然后我需要手动创建 json 并将其放入响应中。

So here is my custom filter:

所以这是我的自定义过滤器:

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

And then i added it in the web.xml before the CorsFilter. And it works!

然后我将它添加到 web.xml 之前的CorsFilter. 它有效!

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

回答by user2669657

It's strange because @ControllerAdvice should works, are you catching the correct Exception?

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

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

Also try to catch this exception in CorsFilter and send 500 error, something like this

还尝试在 CorsFilter 中捕获此异常并发送 500 错误,类似这样

@ExceptionHandler(DataAccessException.class)
@ResponseBody
public String handleDataException(DataAccessException ex, HttpServletResponse response) {
    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    //Json return
}

回答by holmis83

If you want a generic way, you can define an error page in web.xml:

如果你想要一个通用的方式,你可以在 web.xml 中定义一个错误页面:

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

And add mapping in Spring MVC:

并在 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.";
    }
}

回答by Cyva

When you want to test a state of application and in case of a problem return HTTP error I would suggest a filter. The filter below handles all HTTP requests. The shortest solution in Spring Boot with a javax filter.

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

In the implementation can be various conditions. In my case the applicationManager testing if the application is ready.

在执行时可以有各种条件。就我而言,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() {}
}

回答by Raf

I come across this issue myself and I performed the steps below to reuse my ExceptionControllerthat is annotated with @ControllerAdvisefor Exceptionsthrown in a registered Filter.

我遇到这个问题我自己和我进行下面的步骤来重用我ExceptionController被注释与@ControllerAdviseExceptions扔在注册的过滤器。

There are obviously many ways to handle exception but, in my case, I wanted the exception to be handled by my ExceptionControllerbecause I am stubborn and also because I don't want to copy/paste the same code (i.e. I have some processing/logging code in ExceptionController). I would like to return the beautiful JSONresponse just like the rest of the exceptions thrown not from a Filter.

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

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

Anyway, I managed to make use of my ExceptionHandlerand I had to do a little bit of extra as shown below in steps:

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

Steps

脚步



  1. You have a custom filter that may or may not throw an exception
  2. You have a Spring controller that handles exceptions using @ControllerAdvisei.e. MyExceptionController
  1. 您有一个自定义过滤器,可能会也可能不会抛出异常
  2. 您有一个使用@ControllerAdviseie MyExceptionController处理异常的 Spring 控制器

Sample code

示例代码

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

And now the sample Spring Controller that handles Exceptionin normal cases (i.e. exceptions that are not usually thrown in Filter level, the one we want to use for exceptions thrown in a Filter)

现在是Exception在正常情况下处理的示例 Spring Controller (即通常不会在过滤器级别抛出的异常,我们希望用于过滤器中抛出的异常)

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

Sharing the solution with those who wish to use ExceptionControllerfor Exceptionsthrown in a Filter.

与那些希望使用ExceptionControllerfor Exceptionsthrow in a Filter 的人分享解决方案。

回答by walv

This is my solution by overriding default Spring Boot /error handler

这是我通过覆盖默认 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) ;
    }

}

回答by AndyB

So, here's what I did based on an amalgamation of the above answers... We already had a GlobalExceptionHandlerannotated with @ControllerAdviceand I also wanted to find a way to re-use that code to handle exceptions that come from filters.

所以,这是我根据上述答案的合并所做的......我们已经有一个GlobalExceptionHandler注释,@ControllerAdvice我还想找到一种方法来重用该代码来处理来自过滤器的异常。

The simplest solution I could find was to leave the exception handler alone, and implement an error controller as follows:

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

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

So, any errors caused by exceptions first pass through the ErrorControllerand are re-directed off to the exception handler by rethrowing them from within a @Controllercontext, whereas any other errors (not caused directly by an exception) pass through the ErrorControllerwithout modification.

因此,由异常引起的任何错误首先通过ErrorController并通过从@Controller上下文中重新抛出它们而被重定向到异常处理程序,而任何其他错误(不是由异常直接引起的)直接通过ErrorController

Any reasons why this is actually a bad idea?

这实际上是一个坏主意的任何原因?

回答by Tom Bunting

Just to complement the other fine answers provided, as I too recently wanted a singleerror/exception handling component in a simple SpringBoot app containing filters that may throw exceptions, with other exceptions potentially thrown from controller methods.

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

Fortunately, it seems there is nothing to prevent you from combining your controller advice with an override of Spring's default error handler to provide consistent response payloads, allow you to share logic, inspect exceptions from filters, trap specific service-thrown exceptions, etc.

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

E.g.

例如


@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";
  }

}

回答by ssc-hrep3

I wanted to provide a solution based on the answer of @kopelitsa. The main differences being:

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

  1. Reusing the controller exception handling by using the HandlerExceptionResolver.
  2. Using Java config over XML config
  1. 通过使用HandlerExceptionResolver.
  2. 在 XML 配置上使用 Java 配置


First, you need to make sure, that you have a class that handles exceptions occurring in a regular RestController/Controller (a class annotated with @RestControllerAdviceor @ControllerAdviceand method(s) annotated with @ExceptionHandler). This handles your exceptions occurring in a controller. Here is an example using the RestControllerAdvice:

首先,您需要确保您有一个处理常规 RestController/Controller 中发生的异常的类(一个用@RestControllerAdviceor注释的类@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) {
        (...)
    }
}

To reuse this behavior in the Spring Security filter chain, you need to define a Filter and hook it into your security configuration. The filter needs to redirect the exception to the above defined exception handling. Here is an example:

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

@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 (Exception e) {
            log.error("Spring Security Filter Chain Exception:", e);
            resolver.resolveException(request, response, null, e);
        }
    }
}

The created filter then needs to be added to the SecurityConfiguration. You need to hook it into the chain very early, because all preceding filter's exceptions won't be caught. In my case, it was reasonable to add it before the LogoutFilter. See the default filter chain and its order in the official docs. Here is an example:

然后需要将创建的过滤器添加到 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)
            (...)
    }

}

回答by Adewagold

After reading through different methods suggested in the above answers, I decided to handle the authentication exceptions by using a custom filter. I was able to handle the response status and codes using an error response class using the following method.

在阅读了上述答案中建议的不同方法后,我决定使用自定义过滤器来处理身份验证异常。我能够使用以下方法使用错误响应类处理响应状态和代码。

I created a custom filter and modified my security config by using the addFilterAfter method and added after the CorsFilter class.

我创建了一个自定义过滤器并使用 addFilterAfter 方法修改了我的安全配置,并添加到 CorsFilter 类之后。

@Component
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    //Cast the servlet request and response to HttpServletRequest and HttpServletResponse
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    // Grab the exception from the request attribute
    Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
    //Set response content type to application/json
    httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);

    //check if exception is not null and determine the instance of the exception to further manipulate the status codes and messages of your exception
    if(exception!=null && exception instanceof AuthorizationParameterNotFoundException){
        ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = httpServletResponse.getWriter();
        writer.write(convertObjectToJson(errorResponse));
        writer.flush();
        return;
    }
    // If exception instance cannot be determined, then throw a nice exception and desired response code.
    else if(exception!=null){
            ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
            PrintWriter writer = httpServletResponse.getWriter();
            writer.write(convertObjectToJson(errorResponse));
            writer.flush();
            return;
        }
        else {
        // proceed with the initial request if no exception is thrown.
            chain.doFilter(httpServletRequest,httpServletResponse);
        }
    }

public String convertObjectToJson(Object object) throws JsonProcessingException {
    if (object == null) {
        return null;
    }
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(object);
}
}

SecurityConfig class

安全配置类

    @Configuration
    public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    AuthFilter authenticationFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterAfter(authenticationFilter, CorsFilter.class).csrf().disable()
                .cors(); //........
        return http;
     }
   }

ErrorResponse class

错误响应类

public class ErrorResponse  {
private final String message;
private final String description;

public ErrorResponse(String description, String message) {
    this.message = message;
    this.description = description;
}

public String getMessage() {
    return message;
}

public String getDescription() {
    return description;
}}