弹簧过滤器的异常处理

时间:2013-07-18 06:18:39

标签: spring spring-mvc exception-handling

我使用@ExceptionHandler在spring中处理异常。使用@ExceptionHandler注释的方法捕获控制器抛出的任何异常,并相应地采取操作。为了避免为每个使用@ControllerAdvice注释的控制器编写@exceptionHandler。

一切都按预期工作正常。

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

当我从上面的过滤器中抛出相同的异常时,它没有像在控制器情况下那样完成。它直接抛给用户。

这里有什么问题?

8 个答案:

答案 0 :(得分:18)

过滤器在控制器被解析之前发生,因此控制器建议无法捕获从过滤器抛出的异常。

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

答案 1 :(得分:3)

据推测,您希望将HTTP状态代码设置为过滤器中抛出异常的结果?如果是这样,只需设置状态如下:

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

答案 2 :(得分:1)

由于没有从控制器而是过滤器引发异常,@ ControllerAdvice不会抓住它。

所以,我在寻找到处后找到的最佳解决方案是为这个内部错误创建一个过滤器:

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

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

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

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
}

错误类:

{{1}}

答案 3 :(得分:1)

由于异常不是由控制器抛出的,除非您提供自定义过滤器来委托您的异常,否则控制器建议不会捕获异常。

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

例如:

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

答案 4 :(得分:0)

检查以下代码段,它适用于我。

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

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

答案 5 :(得分:0)

如果像我一样,你仍然坚持使用春季3.1(只有@ControllerAdvice后面的0.1个版本),你可以尝试我想出的这个解决方案。

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

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

当然你不会忘记注册你的遗体

然后创建一个在你的desiered过滤器之前调用的过滤器(可选择所有的&#39; em)

然后在那个过滤器

try{
   chain.doFilter(request, response);
catch(Exception e){
   exceptionResolver(request, response, exceptionHandler, e);
   //Make the processing stop here... 
   return; //just in case
}

答案 6 :(得分:0)

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

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

答案 7 :(得分: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));
    }
}