17

我在春天使用@ExceptionHandler 处理异常。控制器抛出的任何异常都使用带有 @ExceptionHandler 注释的方法捕获,并采取相应的措施。为了避免为每个控制器编写@exceptionHandler,我使用了@ControllerAdvice 注释。

一切都按预期正常工作。

现在我有一个过滤器(是的,不是拦截器来处理某些要求),它是使用 DelegatingFilterProxy 和 ContextLoaderListener 实现的。

当我从上面的过滤器中抛出相同的异常时,它并没有像在控制器案例中那样被捕获。它直接扔给用户。

这里有什么问题?

4

8 回答 8

24

过滤器发生在控制器甚至解决之前,因此从过滤器抛出的异常不能被控制器通知捕获。

过滤器是 servlet 的一部分,而不是真正的 MVC 堆栈。

于 2013-07-18T06:40:07.533 回答
12

由于异常不是从控制器引发的,而是从过滤器引发的,@ControllerAdvice 不会捕获它。

因此,我到处寻找后发现的最佳解决方案是为此内部错误创建一个过滤器:

public class ExceptionHandlerFilter extends OncePerRequestFilter {
    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request, response);

        } catch (JwtException e) {
            setErrorResponse(HttpStatus.BAD_REQUEST, response, e);
            e.printStackTrace();
        } catch (RuntimeException e) {
            e.printStackTrace();
            setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, response, e);
        }
    }

    public void setErrorResponse(HttpStatus status, HttpServletResponse response, Throwable ex){
        response.setStatus(status.value());
        response.setContentType("application/json");
        // A class used for errors
        ApiError apiError = new ApiError(status, ex);
        try {
            String json = apiError.convertToJson();
            System.out.println(json);
            response.getWriter().write(json);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

将其添加到您的配置中,我正在使用 WebSecurityConfigurerAdapter 实现:

// Custom JWT based security filter
httpSecurity
        .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);

// Custom Exception Filter for filter
httpSecurity
        .addFilterBefore(exceptionHandlerFilterBean(), JwtAuthenticationTokenFilter.class);

错误类:

public class ApiError {

    private HttpStatus status;
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    private LocalDateTime timestamp;
    private String message;
    private String debugMessage;

    private ApiError() {
        timestamp = LocalDateTime.now();
    }
    public ApiError(HttpStatus status) {
        this();
        this.status = status;
    }

    public ApiError(HttpStatus status, Throwable ex) {
        this();
        this.status = status;
        this.message = "Unexpected error";
        this.debugMessage = ex.getLocalizedMessage();
    }

    public ApiError(HttpStatus status, String message, Throwable ex) {
        this();
        this.status = status;
        this.message = message;
        this.debugMessage = ex.getLocalizedMessage();
    }

    public String convertToJson() throws JsonProcessingException {
        if (this == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        return mapper.writeValueAsString(this);
    }

  //Setters and getters
}
于 2017-09-20T21:36:45.477 回答
3

大概,您想将 HTTP 状态代码设置为过滤器中抛出异常的结果?如果是这样,只需将状态设置如下:

HttpServletResponse 响应 = (HttpServletResponse) res; response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

于 2015-12-29T11:38:35.007 回答
2

由于控制器不会引发异常,因此控制器建议不会捕获异常,除非您提供自定义过滤器来委托您的异常。

您可以创建另一个过滤器来将您的异常委托给控制器建议。诀窍是在所有其他自定义过滤器之前提供这个新创建的过滤器。

例如:

  1. 创建一个新的过滤器来委派您的异常

    @Component
    public class FilterExceptionHandler extends OncePerRequestFilter {
    
    private static Logger logger = LoggerFactory.getLogger(FilterExceptionHandler.class);
    
    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;
    
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } catch (Exception ex) {
            logger.error("Spring Security filter chain exception : {}", ex.getMessage());
            resolver.resolveException(httpServletRequest, httpServletResponse, null, ex);
        }
    }}
    
  2. 如果需要,请创建自定义异常。就我而言,我正在创建一个异常 JukeBoxUnAuthorizedException

    public class JukeBoxUnauthorizedException extends RuntimeException {
      private static final long serialVersionUID = 3231324329208948384L;
      public JukeBoxUnauthorizedException() {
        super();
      }
    
      public JukeBoxUnauthorizedException(String message) {
        super(message);
      }
    
      public JukeBoxUnauthorizedException(String message, Throwable cause) {
        super(message, cause);
      }
    }
    
  3. 创建一个控制器建议来处理这个异常

    @Order(Ordered.HIGHEST_PRECEDENCE)
    @ControllerAdvice
    public class RestExceptionHandler  {
      @ExceptionHandler(value = {JukeBoxUnauthorizedException.class})
      public ResponseEntity<JukeboxResponse> handleUnAuthorizedException(JukeBoxUnauthorizedException exception) {
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ErrorResponse(exception.getMessage()));
      }
    }
    
  4. 在 SecurityConfigurtion 中添加您的异常委托过滤器。即在configure(HttpSecurity http)方法中。请注意,异常委托过滤器应位于层次结构的顶部。它应该在所有自定义过滤器之前

http.addFilterBefore(exceptionHandlerFilter, AuthTokenFilter.class);

于 2021-04-23T12:30:20.893 回答
2

这就是我在过滤器类中抛出错误的方法:

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        if (req.getHeader("Content-Type") == null) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;                
            httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST,
                   "Required headers not specified in the request");            
        }
        chain.doFilter(request, response);
    }
于 2017-04-28T20:54:01.123 回答
0

检查下面的代码片段,它对我有用。

final HttpServletResponseWrapper wrapper = new 
HttpServletResponseWrapper((HttpServletResponse) res);    
wrapper.sendError(HttpServletResponse.SC_UNAUTHORIZED, "<your error msg>");    
res = wrapper.getResponse();

如果您在过滤器中使用它,则添加一个返回语句,否则chain.doFilter(req,res)将覆盖它。

于 2016-03-21T18:32:37.407 回答
0

如果像我一样,你被 spring 3.1 卡住了(只是落后 0.1 版本@ControllerAdvice),你可以试试我刚刚提出的这个解决方案。


所以,你听说过异常解析器,对吧?如果没有,请阅读此处:

@Component
public class RestExceptionResolver extends ExceptionHandlerExceptionResolver {

    @Autowired
    //If you have multiple handlers make this a list of handlers
    private RestExceptionHandler restExceptionHandler;
    /**
     * This resolver needs to be injected because it is the easiest (maybe only) way of getting the configured MessageConverters
     */
    @Resource
    private ExceptionHandlerExceptionResolver defaultResolver;

    @PostConstruct
    public void afterPropertiesSet() {
        setMessageConverters(defaultResolver.getMessageConverters());
        setOrder(2); // The annotation @Order(2) does not work for this type of component
        super.afterPropertiesSet();
    }

    @Override
    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
        ExceptionHandlerMethodResolver methodResolver = new ExceptionHandlerMethodResolver(restExceptionHandler.getClass());
        Method method = methodResolver.resolveMethod(exception);
        if (method != null) {
            return new ServletInvocableHandlerMethod(restExceptionHandler, method);
        }
        return null;
    }

    public void setRestExceptionHandler(RestExceptionHandler restExceptionHandler) {
        this.restExceptionHandler = restExceptionHandler;
    }

    public void setDefaultResolver(ExceptionHandlerExceptionResolver defaultResolver) {
        this.defaultResolver = defaultResolver;
    }
}

然后一个示例处理程序将如下所示

@Component
public class RestExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ResponseBody
    public Map<String, Object> handleException(ResourceNotFoundException e, HttpServletResponse response) {
        Map<String, Object> error = new HashMap<>();
        error.put("error", e.getMessage());
        error.put("resource", e.getResource());
        return error;
    }
 }

当然你不会忘记注册你的bes


然后创建一个在您想要的过滤器之前调用的过滤器(可选全部)

然后在那个过滤器中

try{
   chain.doFilter(request, response);
catch(Exception e){
   exceptionResolver(request, response, exceptionHandler, e);
   //Make the processing stop here... 
   return; //just in case
}
于 2016-11-07T11:04:24.360 回答
0

我用rest api构建了我的应用程序,所以我通过在可能引发异常的过滤器中捕获它然后写回一些东西来解决这个问题。请记住,filterChain.doFilter(request, response);必须包括在内。

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        // something may throw an exception
        filterChain.doFilter(request, response);
    } catch (Exception e) {
        // ResponseWrapper is a customized class
        ResponseWrapper responseWrapper = new ResponseWrapper().fail().msg(e.getMessage());
        response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.getWriter().write(JSON.toJSONString(responseWrapper));
    }
}
于 2018-01-24T15:25:18.640 回答