拦截器与过滤器详解

拦截器和过滤器的使用场景?

拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:

1.登录验证,判断用户是否登录

2.权限验证,判断用户是否有权限访问资源,如校验token

3.日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量

4.处理cookie、本地化、国际化、主题等

5.性能监控:监控请求的处理时长等

通用行为:读取cookie得到用户信息并将用户对象放入请求,以便后续流程使用,还有提取Locale、Theme等信息,只要是多个处理器需要的即可使用拦截器实现

过滤器本质是函数回调(职责链),主要的应用场景包括:

1.过滤敏感词汇

2.设置字符编码

3.URL级别的权限访问控制

4.压缩响应信息

过滤器原理

当我们使用过滤器时,过滤器会对几乎所有的请求进行过滤,过滤器可以动态的分为三个部分:

  • 1.请求进入Tomcat等容器前:对请求进行第一次过滤,然后再执行下面的代码
  • 2.过滤器将请求放行,如果还有其他过滤器,那么就继续交给下一个过滤器
  • 3.Servlet处理完之后:对响应的内容进行再次过滤

 自定义过滤器(IDEA)

1.创建一个普通的Maven项目,导入相关依赖

  <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>3.0.4</version>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.0.4</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
        </dependencies>

2.为了测试,创建一个输入评论的网页

<!DOCTYPE html>
<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>评论</title>
</head>
<body>
<h1>输入评论内容</h1>
<form th:action="@{/comment/submit}" method="post">
    <textarea name="message" cols="30" rows="10"></textarea>
    <input type="submit" value="提交">
</form>
<p th:text="${comment}"></p>
</body>
</html>

3.创建一个filter包,以后我们的过滤器就放在这个包下,然后创建CommentFilter过滤器,实现Filter接口,通过@WebFilter注解,进行一些简单的配置

@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。

下面是一些属性解释:

  • filterName:过滤器名
  • urlPatterns:过滤器的URL匹配模式,这里可以配置你要过滤的请求
  • initParam:设置一些初始参数

注意:jakarta.servlet.Filter接口,不要导错包啦

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(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);
    }
}

4.创建一个controller软件包,在包下创建一个CommentController来处理请求

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";
    }
}

5.创建启动类,启动测试!

@ServletComponentScan:将过滤器注册到Spring容器中

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class ApplicationDemo {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationDemo.class,args);

    }
}

6.访问 http://localhost:8080/comments 网页,故意输入敏感词汇:(可以输入"我是nt"),点击提交,可以看到过滤器的处理结果。

 

 过滤器的生命周期

Filter有三个阶段:

  • 1.初始化阶段:当服务器启动时,我们的服务器(Tomcat)就会读取配置文件,扫描注解,然后来创建Filter
  • 2.拦截和过滤节点:只要请求资源的路径和拦截的路径相同,那么过滤器就会对请求进行过滤,这个阶段在服务器运行过程中会一直循环
  • 3.销毁阶段:当服务器(Tomcat)关闭时,服务器创建的Filter也会随之销毁

可以测试一下:

package com.demo.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;
@WebFilter(filterName = "BFilter",urlPatterns = "/*")
public class BFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("过滤器初始化");
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("BFilter处理请求~");
        chain.doFilter(request,response);
        System.out.println("BFilter处理响应~");

    }

    @Override
    public void destroy() {
        System.out.println("过滤器销毁啦");
        Filter.super.destroy();
    }
}

 测试结果如下:

多个Filter的执行顺序

如果我们是在web.xml中配置的过滤器,那么过滤器的执行顺序就是<filter-mapping>在web的配置顺序,配置在前就会先执行

若我们是使用注解@WebFilter进行配置的,那么执行顺序就是按照过滤器类的字典顺序来执行的,比如AFilter和BFilter过滤器,就会先执行AFilter,因为AFilter字典序在前。

如果是注解和xml混合使用,那么在web.xml中配置的先执行。

拦截器实现

1.在上面的环境下,新建软件包interceptor,专门放拦截器类,创建一个MyInterceptor拦截器

package com.demo.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器前置处理~");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器后置处理~");
           }
}

 2.写一个配置类,注册我们自定义的拦截器:新建一个config包,专门放我们的配置类

package com.demo.config;

import com.demo.interceptor.MyInterceptor;
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.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    MyInterceptor myInterceptor;
    /**
     * 配置静态资源,比如html,js,css等
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

    }

    /**
     * 注册拦截器,我们自定义的拦截器需要在这里注册才会生效
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns();
    }
}

3.启动测试,输入:localhost:8080/,查看输出结果

 拦截器的工作原理

一个拦截器,只有preHandle方法返回true,postHandle、afterCompletion才有可能被执行,如果preHandle方法返回false,那么该拦截器的postHandle、afterCompletion必然不会被执行。

所有的拦截器(Interceptor)和处理器(Handler)都注册在HandleMapping中

Spring MVC中所有的请求都是由DispatcherServlet分发的

当请求进入DispatcherServlet.doDispatch()的时候,首先会得到处理该请求的Handler(即Controller中对应的方法)以及所有拦截该请求的拦截器,拦截器就是在这里被调用开始工作的

拦截器正常的工作流程

中断流程

如果在Interceptor1.preHandle中报错或返回false ,那么接下来的流程就会被中断,但注意被执行过的拦截器的afterCompletion仍然会执行。 

拦截器中的三个方法:

preHandle()方法:该方法会在控制方法前执行,方法返回值表示是否知道如何写一个接口。中断后续操作。当其返回值为true时,表示继续向下执行;当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)

postHandle()方法:该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步修改

afterCompletion()方法:该方法会在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作

拦截器和过滤器的区别

1、实现原理不同

过滤器的本质是函数回调

拦截器是基于java反射机制(动态代理),本质是面向切面编程

2、使用范围不同

我们看到过滤器(Filter)实现的是jakarta.servlet.Filter接口,而这个接口是在Servlet规范中定义的,也就是意味着过滤器使用要依赖Tomcat等容器,导致它只能在web应用中使用

而拦截器(Interceptor)是Spring的一个组件,并由Spring容器管理,不依赖Tomcat等容器,可以单独使用。不仅能应用在web程序中,也可以用于Application/Swing等程序中

3、触发时机不同

过滤器:在请求进入Servlet之前被触发,在响应返回视图之前也会被触发一次

拦截器:在请求进入Servlet之后,进入Controller之前被触发

 4.处理的范围不同

过滤器几乎可以对所有请求进行拦截,而拦截器知会对要进入Controller层请求资源的请求进行拦截

感谢下面大佬的文章:

(69 封私信 / 2 条消息) Spring 拦截器和过滤器的区别? - 知乎 (zhihu.com)icon-default.png?t=N176https://www.zhihu.com/question/30212464/answer/1786967139什么是拦截器?拦截器如何配置? - 北根娃 - 博客园 (cnblogs.com)icon-default.png?t=N176https://www.cnblogs.com/alanlin/p/16267497.html(18条消息) JavaWeb过滤器(Filter)详解,是时候该把过滤器彻底搞懂了(万字说明)_webfilter_秃头披风侠.的博客-CSDN博客icon-default.png?t=N176https://blog.csdn.net/m0_51545690/article/details/123677340Springboot过滤器和拦截器详解及使用场景 - 知乎 (zhihu.com)icon-default.png?t=N176https://zhuanlan.zhihu.com/p/340397290#:~:text=%E6%8B%A6%E6%88%AA%E5%99%A8%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%201%20%E7%99%BB%E5%BD%95%E9%AA%8C%E8%AF%81%EF%BC%8C%E5%88%A4%E6%96%AD%E7%94%A8%E6%88%B7%E6%98%AF%E5%90%A6%E7%99%BB%E5%BD%95%E3%80%82%202%20%E6%9D%83%E9%99%90%E9%AA%8C%E8%AF%81%EF%BC%8C%E5%88%A4%E6%96%AD%E7%94%A8%E6%88%B7%E6%98%AF%E5%90%A6%E6%9C%89%E6%9D%83%E9%99%90%E8%AE%BF%E9%97%AE%E8%B5%84%E6%BA%90%EF%BC%8C%E5%A6%82%E6%A0%A1%E9%AA%8Ctoken%203,%E6%97%A5%E5%BF%97%E8%AE%B0%E5%BD%95%EF%BC%8C%E8%AE%B0%E5%BD%95%E8%AF%B7%E6%B1%82%E6%93%8D%E4%BD%9C%E6%97%A5%E5%BF%97%EF%BC%88%E7%94%A8%E6%88%B7ip%EF%BC%8C%E8%AE%BF%E9%97%AE%E6%97%B6%E9%97%B4%E7%AD%89%EF%BC%89%EF%BC%8C%E4%BB%A5%E4%BE%BF%E7%BB%9F%E8%AE%A1%E8%AF%B7%E6%B1%82%E8%AE%BF%E9%97%AE%E9%87%8F%E3%80%82%204%20%E5%A4%84%E7%90%86cookie%E3%80%81%E6%9C%AC%E5%9C%B0%E5%8C%96%E3%80%81%E5%9B%BD%E9%99%85%E5%8C%96%E3%80%81%E4%B8%BB%E9%A2%98%E7%AD%89%E3%80%82%205%20%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7%EF%BC%8C%E7%9B%91%E6%8E%A7%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E6%97%B6%E9%95%BF%E7%AD%89%E3%80%82%206%20%E9%80%9A%E7%94%A8%E8%A1%8C%E4%B8%BA%EF%BC%9A%E8%AF%BB%E5%8F%96cookie%E5%BE%97%E5%88%B0%E7%94%A8%E6%88%B7%E4%BF%A1%E6%81%AF%E5%B9%B6%E5%B0%86%E7%94%A8%E6%88%B7%E5%AF%B9%E8%B1%A1%E6%94%BE%E5%85%A5%E8%AF%B7%E6%B1%82%EF%BC%8C%E4%BB%8E%E8%80%8C%E6%96%B9%E4%BE%BF%E5%90%8E%E7%BB%AD%E6%B5%81%E7%A8%8B%E4%BD%BF%E7%94%A8%EF%BC%8C%E8%BF%98%E6%9C%89%E5%A6%82%E6%8F%90%E5%8F%96Locale%E3%80%81Theme%E4%BF%A1%E6%81%AF%E7%AD%89%EF%BC%8C%E5%8F%AA%E8%A6%81%E6%98%AF%E5%A4%9A%E4%B8%AA%E5%A4%84%E7%90%86%E5%99%A8%E9%83%BD%E9%9C%80%E8%A6%81%E7%9A%84%E5%8D%B3%E5%8F%AF%E4%BD%BF%E7%94%A8%E6%8B%A6%E6%88%AA%E5%99%A8%E5%AE%9E%E7%8E%B0%EF%BC%89

猜你喜欢

转载自blog.csdn.net/qq_56919740/article/details/129669757