SpringBoot2.X 过滤器-拦截器实战
SpringBoot2.x 过滤器
什么是过滤器?
过滤器可以比喻成打鱼的渔网,渔网有网孔大小,会过滤掉小于网孔大小的🐟;
引申在Web容器中,过滤器可以做:过滤一些敏感的字符串【规定不能出现敏感字符串】、避免中文乱码【规定Web资源都使用UTF-8编码】、权限验证等等等,过滤器的作用非常大,只要发挥想象就可以有意想不到的效果
Filter是如何实现拦截的?
Filter
接口中有一个叫做 doFilter
的方法,这个方法实现了对用户请求的过滤。具体流程大体是这样的:
- 用户发送请求到 web 服务器,请求会先到过滤器;
- 过滤器会对请求进行一些处理比如过滤请求的参数、修改返回给客户端的 response 的内容、判断是否让用户访问该接口等等。
- 用户请求响应完毕。
- 进行一些自己想要的其他操作。
如何自定义过滤器?
下面提供两种方法。
方法一:自己手动注册配置实现
自定义的 Filter 需要实现javax.Servlet.Filter
接口,并重写接口中定义的3个方法。
MyFilter.java
@Component
public class MyFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(MyFilter.class);
@Override
public void init(FilterConfig filterConfig) {
logger.info("初始化过滤器:", filterConfig.getFilterName());
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//对请求进行预处理
logger.info("过滤器开始对请求进行预处理:");
HttpServletRequest request = (HttpServletRequest) servletRequest;
String requestUri = request.getRequestURI();
System.out.println("请求的接口为:" + requestUri);
long startTime = System.currentTimeMillis();
//通过 doFilter 方法实现过滤功能
filterChain.doFilter(servletRequest, servletResponse);
// 上面的 doFilter 方法执行结束后用户的请求已经返回
long endTime = System.currentTimeMillis();
System.out.println("该用户的请求已经处理完毕,请求花费的时间为:" + (endTime - startTime));
}
@Override
public void destroy() {
logger.info("销毁过滤器");
}
}
MyFilterConfig.java
在配置中注册自定义的过滤器。
@Configuration
public class MyFilterConfig {
@Autowired
MyFilter myFilter;
@Bean
public FilterRegistrationBean<MyFilter> thirdFilter() {
FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(myFilter);
filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));
return filterRegistrationBean;
}
}
方法二:通过提供好的一些注解实现
在自己的过滤器的类上加上@WebFilter
然后在这个注解中通过它提供好的一些参数进行配置。
@WebFilter(filterName = "MyFilterWithAnnotation", urlPatterns = "/api/*")
public class MyFilterWithAnnotation implements Filter {
......
}
另外,为了能让 Spring 找到它,你需要在启动类上加上 @ServletComponentScan
注解。
@SpringBootApplication
@ServletComponentScan
public class SpringbootFilterApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootFilterApplication.class, args);
}
}
定义多个拦截器,并决定它们的执行顺序
MyFilter2.java
@Component
public class MyFilter2 implements Filter {
private static final Logger logger = LoggerFactory.getLogger(MyFilter2.class);
@Override
public void init(FilterConfig filterConfig) {
logger.info("初始化过滤器2");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//对请求进行预处理
logger.info("过滤器开始对请求进行预处理2:");
HttpServletRequest request = (HttpServletRequest) servletRequest;
String requestUri = request.getRequestURI();
System.out.println("请求的接口为2:" + requestUri);
long startTime = System.currentTimeMillis();
//通过 doFilter 方法实现过滤功能
filterChain.doFilter(servletRequest, servletResponse);
// 上面的 doFilter 方法执行结束后用户的请求已经返回
long endTime = System.currentTimeMillis();
System.out.println("该用户的请求已经处理完毕,请求花费的时间为2:" + (endTime - startTime));
}
@Override
public void destroy() {
logger.info("销毁过滤器2");
}
}
在配置中注册自定义的过滤器,通过FilterRegistrationBean
的setOrder
方法可以决定 Filter 的执行顺序。
需要注意的是 order 的数字越小优先级越高,越优先执行
@Configuration
public class MyFilterConfig {
@Autowired
MyFilter myFilter;
@Autowired
MyFilter2 myFilter2;
@Bean
public FilterRegistrationBean<MyFilter> setUpMyFilter() {
FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setOrder(0);
filterRegistrationBean.setFilter(myFilter);
filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean<MyFilter2> setUpMyFilter2() {
FilterRegistrationBean<MyFilter2> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setFilter(myFilter2);
filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));
return filterRegistrationBean;
}
}
自定义 Controller 验证过滤器
@RestController
@RequestMapping("/api")
public class MainController {
@GetMapping("/hello")
public String getHello() throws InterruptedException {
Thread.sleep(1000);
return "Hello";
}
}
实际测试效果如下:
SpringBoot2.x 拦截器
什么是拦截器?
当你来到公司并想见见公司经理时,你需要通过拦截器,这些拦截器可以是门卫或者接待员
在Spring中,当请求发送到Controller
时,在被Controller
处理之前,它必须经过拦截器(0或多个)。
Spring Interceptor
是一个非常类似于Servlet Filter
的概念。
Spring Interceptor
仅适用于发送到Controller
的请求。
您可以使用Interceptor来执行某些任务,例如在Controller处理请求之前编写日志,添加或更新配置,...
如下图所示:LogInterceptor 拦截器、MainController 主控制器
访问一个路径为 "/test" 的请求,在到达MainController之前,先被 LogInterceptor 的 preHandle方法处理,我们可以在该方法中打印日志或者校验某些入参,可以记录下访问时间;MainController 的 test 方法处理完请求之后 Response 给用户之前,被 postHandle 方法处理可以记录下访问结束时间,并统计该请求的处理时间,方便开发人员进行性能分析;最后整个请求处理完成后,会执行 afterCompletion 方法。
![img](F:\project\小滴课堂移动端在线项目\第 9 天 新版Servlet3.0和SpringBoot2.X过滤器-拦截器实战\14286863.png)
如何自定义拦截器?
自定义 Interceptor
必须实现org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类。
需要实现三种抽象方法:
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView)
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex)
注意:preHandle方法返回true或false。 如果返回true,则意味着请求将继续执行。
每个请求可能会通过许多拦截器。下图说明了这一点。
SpringBoot 拦截器实战
案例背景:
本项目采用 SpringBoot + thymeleaf 模板引擎进行交互,提供三种请求和三个拦截器,帮助大家理解请求在拦截器中的流转过程。 LogInterceptor 拦截所有请求,并且第一顺位执行,OldLoginInterceptor 仅拦截 "/admin/oldLogin" 请求,第二顺位执行,AdminInterceptor 拦截 "/admin/*" 下除了 "/admin/oldLogin" 之外的三个请求,并且最后执行。
项目结构:
![image-20200607230857070](F:\project\小滴课堂移动端在线项目\第 9 天 新版Servlet3.0和SpringBoot2.X过滤器-拦截器实战\image-20200607230857070.png)
LogInterceptor** 拦截所有请求:
public class LogInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
System.out.println("\n-------- LogInterception.preHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("Start Time: " + System.currentTimeMillis());
request.setAttribute("startTime", startTime);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, //
Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("\n-------- LogInterception.postHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
// You can add attributes in the modelAndView
// and use that in the view page
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("\n-------- LogInterception.afterCompletion --- ");
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("End Time: " + endTime);
System.out.println("Time Taken: " + (endTime - startTime));
}
}
OldLoginInterceptor 拦截器用于将请求 "/admin/oldLogin" 重定向到请求 "/admin/login" 路径上
public class OldLoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("\n-------- OldLoginInterceptor.preHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("Sorry! This URL is no longer used, Redirect to /admin/login");
response.sendRedirect(request.getContextPath() + "/admin/login");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, //
Object handler, ModelAndView modelAndView) throws Exception {
// This code will never be run.
System.out.println("\n-------- OldLoginInterceptor.postHandle --- ");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, //
Object handler, Exception ex) throws Exception {
// This code will never be run.
System.out.println("\n-------- QueryStringInterceptor.afterCompletion --- ");
}
}
AdminInterceptor 拦截除了 "/admin/oldLogin" 之外的匹配 "/admin/*" 路径的请求:
public class AdminInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("\n-------- AdminInterceptor.preHandle --- ");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, //
Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("\n-------- AdminInterceptor.postHandle --- ");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, //
Object handler, Exception ex) throws Exception {
System.out.println("\n-------- AdminInterceptor.afterCompletion --- ");
}
}
配置拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
* 处理所有请求,该拦截器最先被执行
*/
registry.addInterceptor(new LogInterceptor());
/**
* 处理路径为 "/admin/oldLogin" 的请求
* 该拦截器在 LogInterceptor 之后执行
*/
registry.addInterceptor(new OldLoginInterceptor())//
.addPathPatterns("/admin/oldLogin");
/**
* 该拦截器处理 路径为 "/admin/*" 的所有请求,除了 "/admin/oldLogin"
* 该拦截器为最后执行拦截器, 如果请求在前面被拦截并不通过那么该拦截器将得不到执行
*/
registry.addInterceptor(new AdminInterceptor())//
.addPathPatterns("/admin/*")//
.excludePathPatterns("/admin/oldLogin");
}
}
自定义Controller校验拦截器
@RestController
public class MainController {
@RequestMapping(value = { "/", "/test" })
public String test(Model model) {
System.out.println("\n-------- MainController.test --- ");
System.out.println(" ** You are in Controller ** ");
return "test";
}
// This path is no longer used.
// It will be redirected by OldLoginInterceptor
@Deprecated
@RequestMapping(value = { "/admin/oldLogin" })
public String oldLogin(Model model) {
// Code here never run.
return "oldLogin";
}
@RequestMapping(value = { "/admin/login" })
public String login(Model model) {
System.out.println("\n-------- MainController.login --- ");
System.out.println(" ** You are in Controller ** ");
return "login";
}
}
thymeleaf 模板引擎
test.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot Mvc Interceptor example</title>
</head>
<body>
<div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
<a th:href="@{/}">Home</a>
|
<a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)</a>
</div>
<h3>Spring Boot Mvc Interceptor</h3>
<span style="color:blue;">Testing LogInterceptor</span>
<br/><br/>
See Log in Console..
</body>
</html>
login.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring Boot Mvc Interceptor example</title>
</head>
<body>
<div style="border: 1px solid #ccc;padding: 5px;margin-bottom:10px;">
<a th:href="@{/}">Home</a>
|
<a th:href="@{/admin/oldLogin}">/admin/oldLogin (OLD URL)</a>
</div>
<h3>This is Login Page</h3>
<span style="color:blue">Testing OldLoginInterceptor & AdminInterceptor</span>
<br/><br/>
See more info in the Console.
</body>
</html>
启动项目,测试
测试用户访问 http://localhost:8080/ 的时候, LogInterceptor记录相关信息(页面地址,访问时间),并计算 Web服务器处理请求的时间。另外,页面会被渲染成 test.html
。
当用户访问 http://localhost:8080/admin/oldLogin 也就是旧的登录页面(不再使用)时, OldLoginInterceptor将请求重定向 http://localhost:8080/admin/login 页面会被渲染成正常的登录页面 login.html
。
注意看控制台打印出的信息。
拦截器与过滤器的区别?
过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。
拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。
参考文章:
https://o7planning.org/en/11689/spring-boot-interceptors-tutorial#a14288051
https://snailclimb.gitee.io/springboot-guide/#/./docs/basis/springboot-interceptor