在之前的分享中,我们知道,要在SpringBoot应用程序中,进行全局异常的处理,可以通过@ControllerAdvice注解和@ExceptionHandler注解来实现。
而在Spring Boot项目中,拦截器(Interceptor)机制、过滤器(Filter)机制有可能会导致全局异常处理机制时效,其主要的原因就是二者通常情况下会位于控制器之前,如果在拦截器或者是过滤器中抛出异常,那么异常就不可能到达Controller层,而且在控制层没有出现异常,那么全局异常机制也就无法获取到对应的异常进行处理了。那么下面我们就来看看如何解决这个问题。
以下是一些可能导致全局异常处理机制失效的情况
拦截器中
拦截器直接抛出异常
在处理拦截器操作的时候直接抛出了一场,那么这个异常就不会被@ControllerAdvice 捕获,因为@ControllerAdvice 仅适用于控制器中的异常。如下所示。
public class CustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (someConditionFails()) {
throw new CustomException("Interceptor exception");
}
return true;
}
}
拦截器未将异常传递给控制器
在拦截器中,将异常进行了处理,从而导致了全局异常处理机制无法捕获并且处理相关的异常。
异常未传播到DispatcherServlet
当然如果拦截器处理异常的方式是通过直接对响应请求进行了修改,例如写入了错误的信息,那么这种情况下,这个异常就不会被传播到DispatcherServlet中,从而就会导致全局异常处理机制无法正常捕获并且触发异常处理机制。
解决方法
针对以上出现的问题,我们可以通过如下的集中方式来解决,避免出现异常不能正常被处理的情况。如下所示。
方法1:在拦截器中捕获并处理异常
如果拦截器中需要直接抛出异常并进行处理的话,我们可以直接将拦截器中的请求转换成对应的HTTP的响应,然后直接进行请求响应即可,如下所示。
public class CustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
if (someConditionFails()) {
throw new CustomException("Interceptor exception");
}
} catch (CustomException ex) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.getWriter().write("Custom error message");
return false;
}
return true;
}
}
方法2:使用过滤器(Filter)进行异常处理
当然,我们也可以在过滤器中对相关的异常进行捕获并且将其交给SpringBoot的异常处理机制来对相关的异常进行处理,如下所示。
public class ExceptionHandlingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Exception ex) {
request.setAttribute("javax.servlet.error.exception", ex);
request.getRequestDispatcher("/error").forward(request, response);
}
}
}
当然,这种情况下就要求,能够在Spring Boot请求中配置一个正确的错误处理器。来现实错误信息。
方法3:确保异常传播到控制器层
既然说异常没有正常的传递的控制层导致了异常无法被@ControllerAdvice 捕获和处理。那么我们就想办法通过如下的这种方式将异常传递到Controller控制层中然后进行处理。如下所示。
public class CustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (someConditionFails()) {
throw new CustomException("Interceptor exception");
}
return true;
}
}
配置完拦截器之后,就可以在全局异常处理类中添加对应的异常进行后续的处理操作,如下所示。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomException.class)
public ResponseEntity handleCustomException(CustomException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(HttpStatus.BAD_REQUEST.value(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
通过上面这种方式,就可以保证所有的异常都可以被全局异常处理机制正常进行处理,也就是说无论这些异常是从控制器还是拦截器中抛出,只要是异常那么就可以被全局异常处理机制所处理。
过滤器中的异常处理
上面我们介绍了在拦截器中的异常处理失效情况的处理,那么如果是在过滤器中出现的,又如何进行处理呢。
导致失效的情况分析
- 过滤器优先于控制器执行:在前面也提到了过滤器是在请求到达到控制器之前进行的处理操作,那么也就是说它无法被异常处理机制所捕获进行异常的统计处理。因此在SpringBoot过滤器中抛出的异常不会直接进入到异常处理流程中,既然不会进入那么就会导致异常处理失效。
- 过滤器异常未传播:当然如果在过滤器中直接对异常情况进行了处理,那么跟拦截器一样,异常不会被传播到SpringBoot的异常处理机制中也就是说@ControllerAdvice和@ExceptionHandler将不会被触发。
解决方案
方法1:在过滤器中捕获并处理异常
可以模仿拦截器的处理方式,直接在过滤器中捕获异常,并且将错误信息写入到响应信息中,或者可以将请求通过转发的方式转发到一个通用的错误页面中。如下所示。
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class ExceptionHandlingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Exception ex) {
// 将异常设置为请求属性,然后转发到错误处理端点
request.setAttribute("javax.servlet.error.exception", ex);
request.getRequestDispatcher("/error").forward(request, response);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化代码(如果需要)
}
@Override
public void destroy() {
// 清理代码(如果需要)
}
}
需要确保在SpringBoot的配置类中添加了相关的配置操作,如下所示。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean loggingFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new ExceptionHandlingFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
方法2:确保异常传播到控制器层
既然说,请求无法传播到控制层,那么就需要通过控制传播机制将异常能够传播到控制层,这样就可以被Spring MVC的异常处理机制所捕获并且进行处理。如下所示。
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class PropagateExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Exception ex) {
// 不处理异常,让异常传播到控制器层
throw ex;
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化代码(如果需要)
}
@Override
public void destroy() {
// 清理代码(如果需要)
}
}
方法3:自定义异常处理端点
当然,如果在特殊情况下,需要对相关的异常进行特殊处理,我们还可以通过一个自定义的异常处理器来对相关的异常进行特殊的处理。如下所示。
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity handleGlobalException(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
总结
一般情况下,由于过滤器和拦截器导致的全局异常处理机制失效的情况是比较常见的,如果对SpringMVC相关机制不是很了解的读者可以先补充相关的知识,在上面的方法中,介绍了关于如何避免拦截器和过滤器导致异常处理失效的情况,比这建议,既然全局异常处理是正对与Controller的控制,那么最好是全部交由控制层进行统一的管理,这样可以保证代码的完整性以及统一性。