过滤器(Filter)和拦截器(Interceptor)
参考文章:https://blog.csdn.net/tszc95/article/details/129250183
Filter 过滤器
Filter 过滤器介绍
Filter是sun公司中servlet2.3后增加的一个新功能,在javaEE中定义了一个接口 javax.servlet.Filter(新版本包名:jakarta.servlet)来描述过滤器。
Filter主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理,是个典型的处理链。Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序。它依赖于servlet容器,在实现上,基于函数回调,它可以对几乎所有请求进行过滤。
Filter也可以对用户请求生成响应,这一点与Servlet相同,但实际上很少会使用Filter向用户请求生成响应。并且它是随你的web应用启动而启动的,只初始化一次,以后就可以拦截相关请求,只有当你的web应用停止或重新部署的时候才销毁。
Filter工作原理
Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序,其工作原理是,只要你在web.xml文件配置好要拦截的客户端请求,它都会帮你拦截到请求,此时你就可以对请求或响应(Request、Response)统一处理等。
使用Filter完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
-
当服务器启动,会创建Filter对象,并调用init方法,只调用一次。
-
当访问资源,路径与Filter的拦截路径匹配,会执行Filter中的doFilter方法,
-
当服务器关闭时,会调用Filter的destroy方法来进行销毁操作。
Filter应用场景
在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符、敏感词过滤、响应信息压缩、控制权限、控制转向、做一些业务逻辑判断等。
Filter代码示例
配置方式1:通过xml配置(自己查)
配置方式2:通过@WebFilter注解配置
创建一个filter包,以后我们的过滤器就放在这个包下,然后创建CommentFilter过滤器,实现Filter接口,通过@WebFilter注解,进行一些简单的配置
@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。
下面是一些属性解释:
- filterName:过滤器名
- urlPatterns:过滤器的URL匹配模式,这里可以配置你要过滤的请求
- initParam:设置一些初始参数
package com.demo.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebInitParam;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
// 通过WebFilter注解来配置过滤的范围
@WebFilter(filterName = "commentFilter",urlPatterns = "/comment/submit",initParams = {@WebInitParam(name = "sensitiveWord", value = "nt")})
public class CommentFilter implements Filter {
private List<String> sensitiveWords = new ArrayList<>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//得到敏感词汇
String word = filterConfig.getInitParameter("sensitiveWord");
//加入集合
sensitiveWords.add(word);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//设置编码
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setContentType("text/html;charset=utf-8");
//得到评论
String message = servletRequest.getParameter("message");
for (String sensitiveWord : sensitiveWords) {
//对所有敏感词汇进行过滤
if (message.contains(sensitiveWord)){
//替换敏感词汇
message = message.replace(sensitiveWord, "**");
}
}
//存入request域
servletRequest.setAttribute("comment",message);
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
// Filter的销毁方法
System.out.println("TestFilter destroy");
Filter.super.destroy();
}
}
package com.demo.controller;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class CommentController {
/**
* 跳转到评论网页
*/
@RequestMapping("/comments")
public String toCommentPage() {
return "comment";
}
/**
* 处理提交评论的请求
* @param request
* @param model
* @return
*/
@PostMapping("/comment/submit")
public String getString(HttpServletRequest request, Model model) {
//获取过滤器设置的Attribute
String comment = (String) request.getAttribute("comment");
//将其封装在model里面,以便前端使用Thymeleaf模板获取到
model.addAttribute("comment", request.getAttribute("comment"));
return "comment";
}
}
package com.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
// 在启动类添加@ServletComponentScan:将过滤器注册到Spring容器中
@SpringBootApplication
@ServletComponentScan
public class ApplicationDemo {
public static void main(String[] args) {
SpringApplication.run(ApplicationDemo.class,args);
}
}
PS: 多个filter 实现@WebFilter,难以控制filter 的过滤顺序,与其包名和文件名有关,慎用。
配置方式3 :通过@Bean来配置
//1.初始化Filter
@Component
public class TestFilter2 implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter2 init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//在DispatcherServlet之前执行
System.out.println("doFilter2 before");
filterChain.doFilter(servletRequest, servletResponse);
// 在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后
System.out.println("doFilter2 after");
}
@Override
public void destroy() {
System.out.println("Filter2 destroy");
}
}
@Component
public class TestFilter3 implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter3 init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//在DispatcherServlet之前执行
System.out.println("doFilter3 before");
filterChain.doFilter(servletRequest, servletResponse);
// 在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后
System.out.println("doFilter3 after");
}
@Override
public void destroy() {
System.out.println("Filter3 destroy");
}
}
//2.注册到config
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean testFilter3RegistrationBean() {
FilterRegistrationBean registration = new FilterRegistrationBean(new TestFilter2());
registration.addUrlPatterns("/hello world");
registration.setOrder(1); // 值越小越靠前,此处配置有效
return registration;
}
@Bean
public FilterRegistrationBean testFilter4RegistrationBean() {
FilterRegistrationBean registration = new FilterRegistrationBean(new TestFilter3());
registration.addUrlPatterns("/hello world");
registration.setOrder(2);
return registration;
}
}
Filter有三个阶段:
- 初始化阶段:当服务器启动时,我们的服务器(Tomcat)就会读取配置文件,扫描注解,然后来创建Filter
- 拦截和过滤节点:只要请求资源的路径和拦截的路径相同,那么过滤器就会对请求进行过滤,这个阶段在服务器运行过程中会一直循环
- 销毁阶段:当服务器(Tomcat)关闭时,服务器创建的Filter也会随之销毁
Interceptor 拦截器介绍(只针对spring介绍)
Interceptor 定义
拦截器(Interceptor)和Servlet无关,在SpringMVC中依赖于SpringMVC框架,由SpringMVC框架实现。它是一种可以让你在Action执行之前和Result执行之后进行一些功能处理的机制。
在实现上,基于Java的反射机制,属于**面向切面编程(AOP)**的一种运用。可以用于在某个方法或者字段被访问之前,进行拦截,然后在之前或者之后加入某些统一的处理方法。就是在action的前、后、甚至抛出异常时进行处理,比如动态代理就是拦截器的简单实现。
SpringMVC的拦截器基于HandlerInterceptor接口来实现,所以只要实现HandlerInterceptor接口或者继承它的实现类。
Interceptor 工作原理
拦截器的实现分为两步:
第一步,创建一个普通的拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法;
第二步,将上一步创建的拦截器加入到 Spring Boot 的配置文件中。
preHandle()方法:该方法会在控制方法前执行,方法返回值表示是否知道如何写一个接口。中断后续操作。当其返回值为true时,表示继续向下执行;当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)
postHandle()方法:该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步修改
afterCompletion()方法:该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作
Interceptor 应用场景
日志记录:记录请求操作日志(用户ip,访问时间等)
权限检查:判断用户是否有权限访问资源,如校验token 日志记录
性能监控:记录请求->响应时间,preHandle:记录开始时间,afterCompletion:记录结束时间,开始时间相减去结束时间
登录验证:判断用户是否登录
sign校验,封禁校验等
处理cookie,主题,国际化,本地化等
filter可以实现的功能intercepter基本上都能实现
Interceptor 代码实战
实现HandlerInterceptor
接口的拦截器:
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("preHandle拦截的请求路径是{}",requestURI);
//登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
//放行
return true;
}
// 拦截住。未登录。跳转到登录页
request.setAttribute("msg","请先登录");
// re.sendRedirect("/");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
/**
* 目标方法执行完成以后
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}",modelAndView);
}
/**
* 页面渲染以后
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}",ex);
}
}
拦截器注册到容器中
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()) // 拦截器注册到容器中
.addPathPatterns("/**") // 所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
"/js/**","/aa/**"); // 放行的请求
}
拦截器和过滤器的使用场景?
拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:
- 登录验证,判断用户是否登录
- 权限验证,判断用户是否有权限访问资源,如校验token
- 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量
- 处理cookie、本地化、国际化、主题等
- 性能监控:监控请求的处理时长等
过滤器本质是函数回调(职责链),主要的应用场景包括:
- 过滤敏感词汇
- 设置字符编码
- URL级别的权限访问控制
- 压缩响应信息
通用行为:读取cookie得到用户信息并将用户对象放入请求,以便后续流程使用,还有提取Locale、Theme等信息,只要是多个处理器需要的即可使用拦截器实现
过滤器(Filter)和拦截器(Interceptor) 、servlet、controller的执行顺序
过滤器与拦截器区别
过滤器(Filter)和拦截器(Interceptor)它们的主要区别如下:
-
运行位置不同:
Filter在只在 Servlet 前后起作用,Filter 通常不考虑servlet 的实现。
拦截器能够深入到Controller方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。允许用户介入(hook into)请求的生命周期
在Spring构架的程序中,要优先使用拦截器。几乎所有 Filter 能够做的事情, interceptor 都能够轻松的实现
-
执行顺序不同:
过滤器的执行顺序是由其在web.xml文件中声明的顺序决定的,按照声明的顺序依次执行;
而拦截器的执行顺序是根据其在配置文件中的声明顺序决定的,也就是说拦截器可以指定先后顺序。
-
功能不同:
过滤器主要用于对请求进行预处理和过滤,例如设置字符集、登录验证、日志记录等操作;
而拦截器则主要用于对请求进行流程控制,例如权限验证、参数注入、异常处理等操作。
-
依赖框架不同:
过滤器是基于Servlet规范实现的,不依赖任何特定的框架;
而拦截器则通常是针对特定的框架或库实现的,例如Spring MVC框架中的拦截器。
-
实现原理不同:
过滤器的本质是函数回调
拦截器是基于java反射机制(动态代理),本质是面向切面编程(AOP)