过滤器 Filter

1. 为什么要过滤器

我们先从一个业务说起:

如下图,假如我们有一个登录验证的业务,那么一般的思路就是当浏览器访问 login. jsp 时,保存一个 session,若满足相关登录条件(session 中的数据就是条件)就放行,允许其访问服务器的其他资源。


解读:

  1. 可以看出若要实现上述的业务,我们不得不在每一个地方都设置一个权限检验的代码,这样十分的繁杂,怎么优化呢?
  2. 一个很容易想到的思路就是将其封装起来,写成一个权限函数。这样虽然简洁了不少,但对于程序效率几乎没有任何提升;
  3. 但倘若能有一个方法能够实现所有文件的权限管理就好了,统一调度,统一管理,无疑能解决上述所有的问题,这就是过滤器的基本思想

2. 基本介绍

  1. 过滤器就是一个中央控制器,通过程序员自己 DIY 的一系列逻辑来统一控制某一用户能够访问的资源范围;
  2. Filter 过滤器它是 JavaWeb 的三大组件之一 (Servlet 程序、Listener 监听器、Filter 过滤器);
  3. Filter 过滤器是 JavaEE 的规范,是接口,这就是我们实现控制的逻辑载体;
  4. Filter 过滤器作用:拦截请求,过滤响应。

2.1 基本原理

一图胜千言:

解读:

  1. 过滤器是 Tomcat 调用的;

2.2 入门示例

我们已经说了那么多原理了。是时候以新的视角来看待开头的业务需求了。
还是一图胜千言:

解读:

  1. 若浏览器直接根据 url 访问限制资源,就会被过滤器“发现”,强制其走过滤器的道路,成功的达到了限制的目的;

代码实例

public class ManageFilter implements Filter {  
  
    @Override  
    public void init(FilterConfig filterConfig) throws ServletException {    
        System.out.println("ManageFilter init被调用...");  
    }  
  
    @Override  
    public void doFilter(ServletRequest servletRequest,  
                         ServletResponse servletResponse,  
                         FilterChain filterChain) throws IOException, ServletException {  
  
        System.out.println("ManageFilter doFilter() 被调用=" + (++count));  
  
        //到每次调用该filter时,doFilter就会被调用  
  
        //如果这里,没有调用继续请求的方法filterChain.doFilter(),则就停止  
        //如果继续访问目标资源-> 等价于放行  
  
        //在调用过滤器前,servletRequest对象=request已经被创建并封装  
        //所以:我们这里就可以通过servletRequest获取很多信息, 比如访问url , session  
        //比如访问的参数 ... 就可以做事务管理,数据获取,日志管理等  
        //获取到session  
        //可以继续使用 httpServletRequest 方法.  
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;  
        System.out.println("输入密码=" + httpServletRequest.getParameter("password"));  
        HttpSession session = httpServletRequest.getSession();  
        //获取username session对象, 还可以继续使用  
        Object username = session.getAttribute("username");  
        if (username != null) {  
            //1. 继续访问目标资源url  
            //2. servletRequest 和 servletResponse 对象会传递给目标资源/文件  
            System.out.println("servletRequest=" + servletRequest);  
            System.out.println("日志信息==");  
            System.out.println("访问的用户名=" + username.toString());  
            System.out.println("访问的url=" + httpServletRequest.getRequestURL());  
            System.out.println("访问的IP=" + httpServletRequest.getRemoteAddr());  
            filterChain.doFilter(servletRequest, servletResponse);  
        } else {//说明没有登录过..回到登录页面  
            servletRequest.getRequestDispatcher("/login.jsp").  
                    forward(servletRequest, servletResponse);  
        }  
    }  
  
    @Override  
    public void destroy() {  
        //当filter被销毁时,会调用该方法  
        System.out.println("ManageFilter destroy()被调用..");  
    }  
}

解读:

  1. filter 在 web 顶目启动时,由 tomcat 来创建 filter 实例,只会创建一个;
  2. 会调用 filter 默认的无参构造器,同时会调用 init 方法,只会调用一次;
  3. 在创建 filter实例时,同时会创建一个 FilterConfig 对象,并通过 init 方法传入;
  4. 通过 FilterConfig 对象,程序员可以获取该 filter, 的相关配置信息;
  5. 当一个http请求和该filter的urL-patter,匹配时,就会调用doFilter方法;
  6. 在调用doFilter方法时,tomcat会同时创建ServletRequest和ServletResponse和FilterChain对象,并通过doFilter传入;
  7. 如果后面的请求目标资源(jsp,servlet.·)会使用到request,和response,那么会继续传递;

2.3 过滤器的 web. xml 配置

2.3.1 基本说明

以下是配置文件截取的一部分:

<filter>  
    <filter-name>topicFilter</filter-name>  
    <filter-class>com.yelanyanyu.homework.topicFilter</filter-class>  
    <init-param>  
        <param-name>forbiddenWords</param-name>  
        <param-value>苹果,香蕉</param-value>  
    </init-param>  
</filter>  
<filter-mapping>  
    <filter-name>topicFilter</filter-name>  
    <url-pattern>/homework/*</url-pattern>  
</filter-mapping>

解读:

  1. filter 的配置和 Servlet 十分的相似。<filter-name> 就代表着 filter 的名字,Tomcat 通过名字得到 <url-pattern> ,假如 tomcat 发现你的请求的资源在 /homework 目录下,就会强迫你走名字为 topicFilter 的过滤器(底层通过 <filter-class> 反射生成 filter 实例);
  2. <init-param> 是初始化参数,常用来存储一些经常用的值,例如文本验证中违禁词;

2.3.2 url-pattern 说明

  1. url-pattern : Filter 的拦截路径, 即浏览器在请求什么位置的资源时,过滤器会进行拦截过滤;
  2. 精确匹配 /a.jsp 对应的请求地址 http://ip [域名]: port/工程路径/a.jsp 会拦截;
  3. 目录匹配 /manage/对应的请求地址 http://ip [域名]: port/工程路径/manage/xx , 即 web 工程 manage 目录下所有资源会拦截;
  4. 后缀名匹配 *.jsp 后缀名可变,比如 *.action *.do 等等对应的请求地址 http://ip [域名]: port/工程路径/xx.jsp , 后缀名为 . jsp 请求会拦截;
    5、Filter 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在

2.4 生命周期

2.4.1 init 初始化

web 工程启动的时候,会执行构造器和 init 方法,ManageFilter 实例常驻内存。

2.4.2 执行阶段

当 HTTP 请求到 TOMCAT,如果匹配到 Filter 的 url-pattern,doFilter () 方法就会被调用。

2.4.3 结束阶段

当停止 web 工程时,调用 destroy() 方法,销毁 Filter 实例。

2.5 FIlterConfig 类

2.5.1 基本介绍

  1. FilterConfig 是 Filter 过滤器的配置类;
  2. Tomcat 每次创建 Filter 的时候,也会创建一个 FilterConfig 对象,这里包含了 Filter 配置文件的配置信息;
  3. FilterConfig 对象作用是获取 filter 过滤器的配置内容;

2.5.2 方法一览

解读:

  1. getFilterName (),获取过滤器名称;
  2. getServletContext (),获取 ServletContext 对象;
  3. getInitParameterNames (),获取所有初始化参数,返回一个枚举;
  4. getInitParameter ("ip"),获取值为 ip 的初始化参数;

2.5.3 应用实例

public class MyFilterConfig implements Filter {  
  
    private String ip; //从配置获取的ip  
  
    @Override  
    public void init(FilterConfig filterConfig) throws ServletException {  
        System.out.println("MyFilterConfig init() 被调用..");  
        //获取过滤器名称  
        String filterName = filterConfig.getFilterName();  
        ip = filterConfig.getInitParameter("ip");  
        ServletContext servletContext = filterConfig.getServletContext();  
        //获取到该filter所有的配置参数(init-params)名  
        Enumeration<String> initParameterNames =  
                filterConfig.getInitParameterNames();  
  
        //遍历枚举  
        while (initParameterNames.hasMoreElements()) {  
            System.out.println("名字=" + initParameterNames.nextElement());  
        }  
  
        System.out.println("filterName= " + filterName);  
        System.out.println("ip= " + ip);  
        System.out.println("servletContext= " + servletContext);  
  
  
    }  
  
    @Override  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
  
        //通过forbidden ip 来进行控制  
        //先获取到访问ip  
        String remoteAddr = servletRequest.getRemoteAddr();  
        if(remoteAddr.contains(ip)) {  
            System.out.println("封杀该网段..");  
            servletRequest.getRequestDispatcher("/login.jsp").  
                    forward(servletRequest,servletResponse);  
            return; //直接返回  
        }  
  
        //继续访问目标资源  
        filterChain.doFilter(servletRequest,servletResponse);  
    }  
  
    @Override  
    public void destroy() {  
  
    }
}

2.6 过滤器链 FilterChain

在处理某些复杂业务时,一个过滤器不够,可以设计多个过滤器共同完成过滤任务,形成过滤器链。

原理分析

如下图:

解读:

  1. 类似于递归的调用,假若浏览器请求 url 满足这 n 个过滤器的 url-pattern,在执行完每个过滤器的前置程序后就会调用 doFilter 方法,进入下一个过滤器;
  2. 当执行到最后一个过滤器的 doFilter 方法之后(已经访问到了相关资源了),会执行后置代码,返回到第 n-1 个过滤器,继续执行其后置程序;
  3. 第 n-1 个过滤器的后置程序执行完之后就会执行第 n-2 个的,以此类推直到执行完 A 过滤器的后置代码;
  4. 最后 tomcat 把相关资源返回给浏览器;
  5. tomcat 怎么知道那么过滤器应该先匹配哪一个(顺序问题)?根据 web. xml 文件中过滤器配置的相对位置决定谁被匹配,越接近文件首的越先被匹配到

2.7 注意事项

  1. 多个 filter 和目标资源在一次 http 请求,在同一个线程中;
  2. 当一个请求 url 和 filter 的 url-pattern 匹配时, 才会被执行, 如果有多个匹配上,就会顺序执行,形成一个 filter 调用链 (底层可以使用一个数据结构搞定) ;
  3. 多个 filter 共同执行时, 因为是一次 http 请求, 使用同一个 request 对象;
  4. 多个 filter 执行顺序,和 web. xml 配置顺序保持一致;
  5. chain. doFilter (req, resp) 方法将执行下一个过滤器的 doFilter 方法, 如果后面没有过滤器,则执行目标资源;
  6. 小结:注意执行过滤器链时, 顺序是 (用前面的案例分析) Http 请求 -> A 过滤器 dofilter () -> A 过滤器前置代码 -> A 过滤器 chain. doFilter () -> B 过滤器 dofilter () -> B 过滤器前置代码 -> B 过滤器 chain. doFilter () -> 目标文件 -> B 过滤器后置代码 -> A 过滤器后置代码 -> 返回给浏览器页面/数据;

3. 应用实例-关键词过滤

3.1 需求

  1. 浏览评论页面 topic. jsp,输入相关评论,可以在 showTopic. jsp 显示评论内容;
  2. 如果发表的评论内容, 有关键字比如 "苹果" "香蕉", 就返回 topic. jsp, 并提示有禁用词;
  3. 要求发表评论到 showTopic. jsp 时,经过过滤器的处理;
  4. 禁用词, 配置在过滤器, 在启动项目时动态的获取, 注意处理中文;

3.2 代码实现

配置如下过滤器:

<filter>  
    <filter-name>topicFilter</filter-name>  
    <filter-class>com.yelanyanyu.homework.topicFilter</filter-class>  
    <init-param>  
        <param-name>forbiddenWords</param-name>  
        <param-value>苹果,香蕉</param-value>  
    </init-param>  
</filter>  
<filter-mapping>  
    <filter-name>topicFilter</filter-name>  
    <url-pattern>/homework/*</url-pattern>  
</filter-mapping>

topic. jsp 页面:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%--  
  Created by IntelliJ IDEA.  Date: 2022/10/27  Time: 21:11  To change this template use File | Settings | File Templates.--%>  
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false"%>  
<html>  
<head>  
    <title>评论</title>  
</head>  
<body>  
<h1>发表对阿凡达电影的评论</h1><br>  
过滤词:苹果,香蕉.${requestScope.get("errorInfo")}<br>  
<form method="post" action="<%=request.getContextPath()%>/homework/showTopic.jsp">  
    内容:<input type="text" name="topic">  
    <input type="submit" value="提交">  
</form>  
</body>  
</html>

showTopic. jsp 页面

<%--  
  Created by IntelliJ IDEA.  Date: 2022/10/27  Time: 21:11  To change this template use File | Settings | File Templates.--%>  
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>  
<html>  
<head>  
    <title>showTopic</title>  
</head>  
<body>  
<%  
    System.out.println("showTopic被调用~");  
%>  
<h1>你输入的文章内容为:</h1><br>  
[<%=request.getParameter("topic")%>]  
</body>  
</html>

过滤器 topicFilter. java:

package com.yelanyanyu.homework;  
  
  
import com.sun.deploy.net.HttpRequest;  
  
import javax.servlet.*;  
import javax.servlet.http.HttpServletRequest;  
import java.io.IOException;  
  
/**  
 * @author yelanyanyu@zjxu.edu.cn 
 * @version 1.0 
 * */
public class topicFilter implements Filter {  
    private String[] forbiddenWords;  
  
    @Override  
    public void init(FilterConfig filterConfig) throws ServletException { 
	    /*
	    得到过滤器的初始化参数(违禁词列表)
		*/ 
        String forbiddenWord = filterConfig.getInitParameter("forbiddenWords");  
        forbiddenWords = forbiddenWord.split(",");  
    }  
  
    @Override  
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
        HttpServletRequest request = (HttpServletRequest) servletRequest;  
        request.setCharacterEncoding("utf-8");  
        String topic = request.getParameter("topic");  
        System.out.println("topic= " + topic);  
        //没有评论或者评论为空就打回评论页面
        //topic为null同时防止用户直接访问限制资源
        if (topic == null || "".equals(topic)) {  
            request.getRequestDispatcher("/topic.jsp").forward(servletRequest, servletResponse);  
            return;  
        }  
        //topic有禁用词就设置request参数,从而在login.jsp显示相关错误信息
        for (String forbiddenWord : forbiddenWords) {  
            if (topic.contains(forbiddenWord)) {  
                request.setAttribute("errorInfo", "有禁用词!!!");  
                request.getRequestDispatcher("/topic.jsp").forward(servletRequest, servletResponse);  
                return;  
            }  
        }  
        //若什么都不违反就放行到showTopic.jsp 页面
        filterChain.doFilter(servletRequest, servletResponse);  
    }  
  
    @Override  
    public void destroy() {  
		
    }
}
posted @ 2023-06-13 18:25  yelanyanyu  阅读(11)  评论(0)    收藏  举报