JavaWeb三大组件之过滤器

JavaWeb三大组件之过滤器

过滤器说明

image-20230425202522350

过滤器介绍

  1. Filter 过滤器它是 JavaWeb 的三大组件之一(Servlet 程序、Listener 监听器、Filter 过滤器)
  2. Filter 过滤器是 JavaEE 的规范,是接口

image-20230425203349863

  1. Filter 过滤器它的作用是:拦截请求,过滤响应

  2. 应用场景

    • 权限检查
    • 日记操作
    • 事务管理

Filter 过滤器基本原理

一图胜千言

image-20230425205723853

Filter 过滤器快速入门

需求:在 web 工程下,有后台管理目录 manage,要求该目录下所有资源(html、图片、jsp、Servlet等)用户登录后才能访问

代码实现:

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>管理后台登录</title>
</head>
<body>
<h1>管理后台登录</h1>
<%
    System.out.println("login.jsp:request=" + request);
%>
<form action="<%=request.getContextPath() %>/loginCheckServlet" method="post">
    u:<input type="text" name="username"/> <br/><br/>
    p:<input type="password" name="password"/> <br/><br/>
    <input type="submit" value="用户登录"/></form>
</body>
</html>

LoginCheckServlet

public class LoginCheckServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取到用户名和密码->DB
        //假设密码是123456, 就可以通过
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        System.out.println("LoginCheckServlet:request=" + request);
        if("123456".equals(password)) {
            //合法, 将用户名,加入session
            request.getSession().setAttribute("username", username);
            //请求转发到admin.jsp
            request.getRequestDispatcher("/manage/admin.jsp")
                    .forward(request,response);
        } else {
            //不合法, 返回登录页面
            request.getRequestDispatcher("/login.jsp")
                    .forward(request,response);
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

创建ManageFilter过滤器

  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,那么会继续传递
public class ManageFilter implements Filter {

    private int count = 0;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //当 Tomcat 创建 Filter 创建,就会调用该方法,进行初始化
        //提醒:回忆我们自己实现 tomcat 底层机制 + servlet 程序, 就会了然
        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 就会被调用
        //如果这里,没有调用继续请求的方法,则就停止
        //如果继续访问目标资源-> 等价于放行

        //说明:在调用过滤器前,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) {
            //解读 filterChain.doFilter(servletRequest, servletResponse)
            //1. 继续访问目标资源 url
            //2. servletRequest 和 servletResponse 对象会传递给目标资源/文件
            //3. 一定要理解 filter 传递的两个对象,再后面的 servlet/jsp 是同一个对象(指的是在一次http请求)
            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()被调用..");
    }
}

在 web.xml 配置过滤器

说明:filter 一般写在其它 servlet 的前面

  1. 通过观察我们发现 filter 配置和 servlet 非常相似. filter 也是被 tomcat 管理和维护
  2. url-pattern 就是当请求的 url 和 匹配的时候,就会调用该filter
  3. /manage/* 第一个 / 解析成 http://ip:port/工程路径
  4. 完整的路径就是 http://ip:port/工程路径/manage/* 当请求的资源 url 满足该条件时
    都会调用 filter , /manage/admin.jsp
<filter>
    <filter-name>ManageFilter</filter-name>
    <filter-class>com.bestpig.filter.ManageFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>ManageFilter</filter-name>
    <url-pattern>/manage/*</url-pattern>
</filter-mapping>

转发到manage目录下的admin.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>后台管理</title>
    <base href="<%=request.getContextPath() %>/manage/"/>
</head>
<body>
<h1>后台管理</h1>
<%
    // 验证request对象是和前面的 filter 是一个对象
    System.out.println("request=" + request);
%>
<a href="#">用户列表</a>||<a href="#">添加用户</a>||<a href="#">删除用户</a>
<hr/>
<img src="tx.png" height="300"/>
</body>
</html>

从 LoginCheckServlet 到 manage 目录下的 admin.jsp,因为是请求转发,故不会经过过滤器,而在 admin.jsp 页面,去请求 tx.png 时则要经过过滤器

控制台输出如下

ManageFilter init被调用...
login.jsp:request=org.apache.catalina.connector.RequestFacade@22921e71
LoginCheckServlet:request=org.apache.catalina.connector.RequestFacade@22921e71
admin.jsp:request=org.apache.catalina.core.ApplicationHttpRequest@75a34792
ManageFilter doFilter() 被调用=1
输入密码=null
servletRequest=org.apache.catalina.connector.RequestFacade@22921e71
日志信息==
访问的用户名=bestpig
访问的url=http://localhost:8080/filter/manage/tx.png
访问的IP=0:0:0:0:0:0:0:1

Filter 过滤器 url-patter

  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 过滤器它只关心请求的地址是否匹配,不关心请求的资源是否存在

Filter 过滤器生命周期

image-20230501113547421

FilterConfig

image-20230501113625749

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

应用实例

FilterConfig1

public class FilterConfig1 implements Filter {

    private String ip; //从配置获取的ip

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("HspFilterConfig init() 被调用..");
        
        //通过filterConfig 获取相关的参数
        String filterName = filterConfig.getFilterName();
        ip = filterConfig.getInitParameter("ip");
        ServletContext servletContext = filterConfig.getServletContext();
        //可以获取到该filter所有的配置参数名
        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 {

    }

    @Override
    public void destroy() {

    }
}

配置 web.xml

<filter>
    <filter-name>FilterConfig1</filter-name>
    <filter-class>com.bestpig.filter.FilterConfig1</filter-class>
    <!--这里就是给该filter配置的参数-有程序员根据业务逻辑来设置-->
    <init-param>
        <param-name>ip</param-name>
        <param-value>127.0</param-value>
    </init-param>
    <init-param>
        <param-name>port</param-name>
        <param-value>8888</param-value>
    </init-param>
    <init-param>
        <param-name>email</param-name>
        <param-value>bestpig@qq.com</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>FilterConfig1</filter-name>
    <url-pattern>/abc/*</url-pattern>
</filter-mapping>

控制台输出

FilterConfig1 init() 被调用..
名字=port
名字=ip
名字=email
filterName= FilterConfig1
ip= 127.0
servletContext= org.apache.catalina.core.ApplicationContextFacade@5ad791ce

FilterChain 过滤器链

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

应用实例

image-20230501191531070

AFilter

public class AFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("AFilter---> 线程id=" +
                Thread.currentThread().getId());
        System.out.println("AFilter doFilter 的前置代码...");
        System.out.println("执行 AFilter doFilter()");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("AFilter doFilter 的后置代码...");
    }

    @Override
    public void destroy() {
    }
}

BFilter

public class BFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("BFilter---> 线程id=" +
                Thread.currentThread().getId());
        System.out.println("BFilter doFilter 的前置代码...");
        System.out.println("执行 BFilter doFilter()");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("BFilter doFilter 的后置代码...");
    }

    @Override
    public void destroy() {
    }
}

admin 下的 hi.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>hi</title>
</head>
<body>
<h1>admin目录下的 hi.jsp</h1>
<h1>后台管理</h1>
<a href="#">用户列表</a>||<a href="#">添加用户</a>||<a href="#">删除用户</a>
<hr/>
</body>
</html>

web.xml 配置

<filter>
    <filter-name>AFilter</filter-name>
    <filter-class>com.bestpig.filter.AFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>AFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

<filter>
    <filter-name>BFilter</filter-name>
    <filter-class>com.bestpig.filter.BFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>BFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

浏览器方位 hi.jsp,控制台输出如下

AFilter---> 线程id=38
AFilter doFilter 的前置代码...
执行 AFilter doFilter()
BFilter---> 线程id=38
BFilter doFilter 的前置代码...
执行 BFilter doFilter()
BFilter doFilter 的后置代码...
AFilter doFilter 的后置代码...

注意事项和细节

  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过滤器后置代码 -> 返回给浏览器页面/数据
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值