SpringBoot自定义拦截器以及多个拦截器执行顺序
一 springboot自定义拦截器
1.1 了解拦截器
springboot所有的拦截器都实现/继承自HandlerInterceptor接口。如果想要编写一个自定义拦截器,就需要实现/继承HandlerInterceptor接口或其子接口/实现类。下图所示为Spring MVC中拦截器的类图
HandlerInterceptor接口的源码如下,包含3个默认实现(Java 8)的方法——preHandle、postHandle和afterCompletion
public interface HandlerInterceptor {//处理器执行前被调用default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}//处理器执行后,视图渲染前被调用default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}//视图渲染完成后被调用default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}
}
先 自 定 义 一 个 最 简 单 、 纯 净 的 拦 截 器 , 也 就 是 直 接 实 现HandlerInterceptor 接 口 。 新 建 一 个 LogInterceptor 类 并 实 现HandlerInterceptor接口
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("afterCompletion");}
}
在3个方法中分别添加了一条日志打印代码。新建一个WebConfigurer类并实现WebMvcConfigurer接口,用于注册自定义的拦截器:
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 WebConfigurer implements WebMvcConfigurer {@Autowiredprivate LogInterceptor logInterceptor;
// @Autowired
// private TimeInterceptor timeInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor);
// registry.addInterceptor(timeInterceptor);}
}
在HelloController类的hello方法中添加一条日志打印代码:
@slf4j
@RestController
public class HelloController {@GetMapping("/hello")public String hello() {log.info("hello");return "Hello Spring Boot";}
}
接下来启动工程,并访问http://localhost:8080/hello,会在控制台看到如下输出:
这代表我们自定义的拦截器成功了!
1.2 拦截器执行流程
从控制台的日志输出中,我们可以大概看出拦截器的执行流程。通过下图,我们可以更清晰地了解拦截器的执行流程。
- 执行preHandle方法。该方法会返回一个布尔值。如果为false,则结束本次请求;如果为true,则继续本次请求。
- 执行处理器逻辑,也就是Controller。
- 执行postHandle方法。
- 执行afterCompletion方法。
二 多个拦截器
在实际应用中,通常需要多个拦截器一起配合使用才能满足我们的需求。了解了单个拦截器的执行流程后,接下来看看多个拦截器组合起来是如何运转的:是执行完一个再执行下一个,还是嵌套执行,抑或是其他的方式呢?下面我们来一探究竟。
再创建一个拦截器 , 用来记录程序执行消耗的时间 , 创建一个TimeInterceptor类,同样实现HandlerInterceptor接口,代码如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;@Slf4j
//@Component
public class TimeInterceptor implements HandlerInterceptor {private final ThreadLocal<LocalTime> threadLocalStart = new ThreadLocal<>();private final ThreadLocal<LocalTime> threadLocalEnd = new ThreadLocal<>();private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss:SSS");// 记录开始时间@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {LocalTime startTime = LocalTime.now();threadLocalStart.set(startTime);log.info("开始时间:{}", startTime.format(formatter));return true;}// 记录结束时间@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {LocalTime endTime = LocalTime.now();threadLocalEnd.set(endTime);log.info("结束时间:{}", endTime.format(formatter));}// 计算接口执行时间@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {LocalTime startTime = threadLocalStart.get();LocalTime endTime = threadLocalEnd.get();log.info("接口执行时间:{} 毫秒", Duration.between(startTime, endTime).getNano() / 1000000);}
}
注册拦截器
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 WebConfigurer implements WebMvcConfigurer {@Autowiredprivate LogInterceptor logInterceptor;@Autowiredprivate TimeInterceptor timeInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor);registry.addInterceptor(timeInterceptor);}
}
接下来启动工程并再次访问hello接口,我们会看到控制台输出如下日志:
通过控制台的输出信息,我们可以看到多个拦截器的执行顺序有些类似于数据结构中的栈——先进后出。下面我们通过图来更加直观地理解一下这个逻辑。