
拦截器是 Spring 框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码。
也就是说,允许开发人员提前预定义一些逻辑,在用户的请求响应前后执行。也可以在用户请求前阻止其执行。 在拦截器当中,开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器来拦截前端发来的请求,判断
Session中是否有登录用户的信息。如果有就可以放行,如果没有就进行拦截。

比如我们去银行办理业务,在办理业务前后,就可以加一些拦截操作:
这些就是"拦截器"做的工作。
拦截器的使用步骤分为两步:
定义拦截器:实现 HandlerInterceptor 接口,并重写其指定方法
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor 目标方法执行前执行..");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor 目标方法执行后执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor 视图渲染完毕后执行,最后执行");
}
}SpringMVC 的拦截器有 三个回调方法:
preHandletrue → 继续往下走;返回 false → 拦截请求,不会再进入 Controller。postHandleafterCompletionfinally)。ThreadLocal.remove()。afterCompletion。注册配置拦截器:实现 WebMvcConfigurer 接口,并重写 addInterceptors() 方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 注入自定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册自定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**"); // 设置拦截器拦截的请求路径,其中 /** 表示拦截所有请求
}
}启动服务,试试访问任意请求,观察后端日志。

可以看到 preHandle 方法执行之后就放行了,开始执行目标方法,目标方法执行完成之后执行 postHandle 和 afterCompletion 方法。
我们把拦截器中 preHandle 方法的返回值改为 false,再观察运行结果。

可以看到,拦截器拦截了请求,没有进行响应。
拦截器的入门程序完成之后,接下来我们来介绍拦截器的使用细节。拦截器的使用细节我们主要介绍两个部分:
拦截路径是指我们定义的这个拦截器,对哪些请求生效。
在注册配置拦截器的时候,通过 addPathPatterns() 方法指定要拦截哪些请求;也可以通过excludePathPatterns() 指定不拦截哪些请求,而 /** 表示拦截所有的请求。
比如用户登录校验,我们希望可以对除了登录之外所有的路径生效。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 设置拦截器拦截的请求路径(/**表示拦截所有请求)
.excludePathPatterns("/user/login") // 设置拦截器排除拦截的路径
.excludePathPatterns("/**/*.js") // 排除前端静态资源
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/*.html");
}
}下面是常见拦截路径设置:
拦截路径 | 含义 | 举例 |
|---|---|---|
/* | 一级路径 | 能匹配 /user、/book、/login,但不能匹配二级目录及更深的路径,比如 /user/login 访问不到 |
/** | 任意级路径 | 能匹配 /user、/user/login、/user/reg 所有路径 |
/book/* | /book 下的一级路径 | 能匹配 /book/addBook,不能匹配 /book/addBook/1、/book |
/book/** | /book 下的任意级路径 | 能匹配 /book、/book/addBook、/book/addBook/2,不能匹配 /user/login |
以上拦截规则可以拦截此项目中的使用URL,包括静态文件(图片文件,JS 和 CSS 等文件)
有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图:

Controller 的方法之前,请求会先被拦截器拦截住。执行 preHandle() 方法,这个方法需要返回一个布尔类型的值。如果返回 true,就表示放行本次操作,继续访问 controller 中的方法。如果返回 false,则不会放行(controller中的方法也不会执行)controller 当中的方法执行完毕后,再回过来执行 postHandle() 这个方法以及 afterCompletion() 方法,执行完毕之后,最终给浏览器响应数据。观察服务启动日志:

当 Tomcat 启动之后,有一个核心的类 DispatcherServlet,它来控制程序的执行顺序。所有请求都会先进到 DispatcherServlet,执行 doDispatch() 调度方法。
如果有拦截器,会先执行拦截器 preHandle() 方法的代码,如果 preHandle() 返回 true,继续访问 controller 中的方法。controller 当中的方法执行完毕后,再回过来执行 postHandle() 和 afterCompletion(),返回给 DispatcherServlet,最终给浏览器响应数据。

DispatcherServlet 的初始化方法 init() 在其父类 HttpServletBean 中实现的。
主要作用是加载 web.xml 中 DispatcherServlet 的配置,并调用子类的初始化。
init() 具体代码如下:
@Override
public final void init() throws ServletException {
try {
// ServletConfigPropertyValues 是静态内部类,使用 ServletConfig 获取 web.xml 中配置的参数
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
// 使用 BeanWrapper 来构造 DispatcherServlet
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException ex) {}
// 让子类实现的方法,这种在父类定义在子类实现的方式叫做模版方法模式
initServletBean();
}在 HttpServletBean 的 init() 中调用了 initServletBean(),它是在 FrameworkServlet 类中实现的,主要作用是建立 WebApplicationContext 容器,并加载 SpringMVC 配置文件中定义的 Bean 到该容器中,最后将该容器添加到 ServletContext 中。
下面是 initServletBean() 的具体代码:
/**
* Overridden method of {@linkHttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// 创建ApplicationContext容器
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}此处打印的日志,也正是控制台打印出来的日志。

初始化 web 容器的过程中,会通过 onRefresh 来初始化 SpringMVC 的容器:
protected WebApplicationContext initWebApplicationContext() {
//...
if (!this.refreshEventReceived) {
//初始化Spring MVC
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
return wac;
}
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}在 initStrategies() 中进行九大组件的初始化,如果没有配置相应的组件,就使用默认定义的组件。
MultipartResolverLocaleResolverThemeResolver HandlerMappings:URL 对应的位置(Controller.xxx方法);如果在 ApplicationContext 发现有 HandlerMappings,则从 ApplicationContext 中获取到所有的 HandlerMappings,并进行排序;如果在 ApplicationContext 中没有发现有处理器映射器,则默认 BeanNameUrlHandlerMapping作为处理器映射器HandlerAdapter:作用是通过调用具体的方法来处理具体的请求;如果在 ApplicationContext 发现有 handlerAdapter,则从 ApplicationContext 中获取到所有的 HandlerAdapter,并进行排序;如果在 ApplicationContext 中没有发现处理器适配器,则默认 SimpleControllerHandlerAdapter 作为处理器适配器HandlerExceptionResolverRequestToViewNameTranslator:其作用是从 Request 中获取 viewNameViewResolversFlashMapManager:其作用是用于检索和保存 FlashMap(保存从一个 URL 重定向到另一个 URL时的参数信息)DispatcherServlet 接收到请求后,执行 doDispatch 调度方法,再将请求转给 Controller。
下面是 doDispatch 方法的具体实现:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 1. 获取执行链
// 遍历所有的 HandlerMapping 找到与请求对应的 Handler
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 2. 获取适配器
// 遍历所有的 HandlerAdapter,找到可以处理该 Handler 的 HandlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
// 3. 执行拦截器 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 4. 执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
// 5. 执行拦截器 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
// 6. 处理视图, 处理之后执行拦截器 afterCompletion 方法
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
// 7. 执行拦截器 afterCompletion 方法
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
HandlerAdapter主要用于支持不同类型的处理器,如Controller、HttpRequestHandler或者Servlet等,让它们能够适配统一的请求处理流程。这样,SpringMVC可以通过一个统一的接口来处理来自各种处理器的请求。
从上述源码可以看出在开始执行 Controller 之前,会先调用预处理方法 applyPreHandle,而 applyPreHandle 方法的实现源码如下:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
// 获取项目中使用的拦截器 HandlerInterceptor
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
}在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor,并执行拦截器中的 preHandle 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:

true,整个发放就返回 true,继续执行后续逻辑处理;fasle,则中断后续操作。统一返回格式:后端接口不论成功或失败,都返回一个结构一致的 JSON 格式响应,让前端不必逐个接口判断数据结构,提升开发效率、减少出错。
统一的数据返回格式使用注解 @ControllerAdvice 和实现 ResponseBodyAdvice 接口的方式实现。
@ControllerAdvice:表示控制器通知类
@Slf4j
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
private static ObjectMapper mapper = new ObjectMapper();
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 防止包装两次
if (body instanceof Result){
return body;
}
// 如果返回结果为String类型, 使用SpringBoot内置提供的Jackson来实现信息的序列化
if (body instanceof String){
return mapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}supports():判断是否要执行 beforeBodyWrite() 方法。true 为执行,false 不执行。通过该方法可以选择哪些类或哪些方法的 response 要进行处理,其他的不进行处理。beforeBodyWrite():对 response 方法进行具体操作处理。需要对 String 特别处理的原因分析:因为 SpringMVC 中返回类型为 String 时,会使用 StringHttpMessageConverter,它不会走对象的 JSON 序列化流程,但是返回类型仍然是 JSON,所以一返回时就会出错。
SpringMVC默认会注册一些自带的HttpMessageConverter(从先后顺序排列分别为ByteArrayHttpMessageConverter,StringHttpMessageConverter,SourceHttpMessageConverter,SourceHttpMessageConverter,AllEncompassingFormHttpMessageConverter),如下所示:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
//...
public RequestMappingHandlerAdapter() {
this.messageConverters = new ArrayList<>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
if (!shouldIgnoreXml) {
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
}
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
//...
}其中 AllEncompassingFormHttpMessageConverter 会根据项目依赖情况添加对应的 HttpMessageConverter:
public AllEncompassingFormHttpMessageConverter() {
if (!shouldIgnoreXml) {
try {
addPartConverter(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
if (jaxb2Present && !jackson2XmlPresent) {
addPartConverter(new Jaxb2RootElementHttpMessageConverter());
}
}
if (kotlinSerializationJsonPresent) {
addPartConverter(new KotlinSerializationJsonHttpMessageConverter());
}
if (jackson2Present) {
addPartConverter(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
addPartConverter(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
addPartConverter(new JsonbHttpMessageConverter());
}
if (jackson2XmlPresent && !shouldIgnoreXml) {
addPartConverter(new MappingJackson2XmlHttpMessageConverter());
}
if (jackson2SmilePresent) {
addPartConverter(new MappingJackson2SmileHttpMessageConverter());
}
}在依赖中引入 jackson 包后,容器会把 MappingJackson2HttpMessageConverter 自动注册到 messageConverters 链的末尾。 Spring 会根据返回的数据类型,从 messageConverters 链选择合适的 HttpMessageConverter。 当返回的数据是非字符串时,使用的 MappingJackson2HttpMessageConverter 写入返回对象。 当返回的数据是字符串时,StringHttpMessageConverter 会先被遍历到,这时会认为StringHttpMessageConverter 可以使用。
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {
//...代码省略
protected <T> void writeWithMessageConverters(@Nullable T value,
MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException,
HttpMessageNotWritableException {
//...代码省略
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter
instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
//getAdvice().beforeBodyWrite 执行之后, body转换成了Result类型的结果
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
//此时cover为StringHttpMessageConverter
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
//...代码省略
}
//...代码省略
}在 ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage) 的处理中,调用父类的 write 方法。 由于 StringHttpMessageConverter 重写了 addDefaultHeaders 方法,所以会执行子类的方法。 然而子类 StringHttpMessageConverter 的 addDefaultHeaders 方法定义接收参数为 String,此时泛型 t 为 Result 类型,所以出现类型不匹配 "Result cannot be cast to java.lang.String" 的异常。
统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的。
@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
具体代码如下:
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
}类名,方法名和返回值可以自定义,重要的是注解。 接口返回为数据时,需要加
@ResponseBody注解。
我们可以针对不同的异常,返回不同的结果:
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
@ExceptionHandler
public Object handler(NullPointerException e) {
return Result.fail("发生NullPointerException:"+e.getMessage());
}
@ExceptionHandler
public Object handler(ArithmeticException e) {
return Result.fail("发生ArithmeticException:"+e.getMessage());
}
}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。