【JavaWeb】快速学习Filter&Listener(一)

简介: 【JavaWeb】快速学习Filter&Listener(一)

1、Filter


1.1 Filter概述


Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。Servlet 之前都已经学习过了,Filter和Listener 在今天这篇文章都会进行学习。


过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。


如下图所示,浏览器可以访问服务器上的所有的资源(servlet、jsp、html等)


1d69e0e209615b04b59b8bf244dd0d51_image-20210823184519509.png


而在访问到这些资源之前可以使过滤器拦截来下,也就是说在访问资源之前会先经过 Filter,如下图


e41c9d59a12d1a8f377b5caaef0d5f5b_image-20210823184657328.png


拦截器拦截到后可以做什么功能呢?


==过滤器一般完成一些通用的操作。==比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。


我们之前做的品牌数据管理的案例中就已经做了登陆的功能,而如果我们不登录能不能访问到数据呢?我们可以在浏览器直接访问首页 ,可以看到 查询所有 的超链接


7e580f284185c71790f66b0acd34040f_image-20210823185720197.png


当我点击该按钮,居然可以看到品牌的数据


5395d1914a28be2361106f0f03dbf517_image-20210823185932418.png


这显然和我们的要求不符。我们希望实现的效果是用户如果登陆过了就跳转到品牌数据展示的页面;如果没有登陆就跳转到登陆页面让用户进行登陆,要实现这个效果需要在每一个资源中都写上这段逻辑,而像这种通用的操作,我们就可以放在过滤器中进行实现。这个就是权限控制,以后我们还会进行细粒度权限控制。过滤器还可以做 统一编码处理、 敏感字符处理 等等…


1.2 Filter快速入门


1.2.1 开发步骤


进行 Filter 开发分成以下三步实现


定义类,实现 Filter接口,并重写其所有方法


fd124a45d5f89e5205ac1b28eb75b6da_image-20210823191006878.png


配置Filter拦截资源的路径:在类上定义 @WebFilter 注解。而注解的 value 属性值 /* 表示拦截所有的资源


f6902a19174d71d75f06851fd44d3fb8_image-20210823191037163.png


在doFilter方法中输出一句话,并放行


8246a18da93b2616efdbb313d3e2d687_image-20210823191200201.png


上述代码中的 chain.doFilter(request,response); 就是放行,也就是让其访问本该访问的资源。


1.2.2 代码演示


创建一个项目,项目下有一个 hello.jsp 页面,项目结构如下:


e0d5e0205e88e7d1cc8b93560f0b1cbd_image-20210823191855765.png


pom.xml 配置文件内容如下:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>filter-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>80</port>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


hello.jsp 页面内容如下:


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>hello JSP~</h1>
</body>
</html>


我们现在在浏览器输入 http://localhost/filter-demo/hello.jsp 访问 hello.jsp 页面,这里是可以访问到 hello.jsp 页面内容的。


16e13e7b4faa2fe80ac6ec31874d2287_image-20210823192353031.png


接下来编写过滤器。过滤器是 Web 三大组件之一,所以我们将 filter 创建在 com.sun.web.filter 包下,起名为 FilterDemo


@WebFilter("/*")
public class FilterDemo implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("FilterDemo...");
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void destroy() {
    }
}



重启启动服务器,再次重新访问 hello.jsp 页面,这次发现页面没有任何效果,但是在 idea 的控制台可以看到如下内容


c508349e819eae11788705a64e1c9024_image-20210823193759365.png


上述效果说明 FilterDemo 这个过滤器的 doFilter() 方法执行了,但是为什么在浏览器上看不到 hello.jsp 页面的内容呢?这是因为在 doFilter() 方法中添加放行的方法才能访问到 hello.jsp 页面。那就在 doFilter() 方法中添加放行的代码


//放行
 chain.doFilter(request,response);


再次重启服务器并访问 hello.jsp 页面,发现这次就可以在浏览器上看到页面效果。


FilterDemo 过滤器完整代码如下:


@WebFilter("/*")
public class FilterDemo implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("1.FilterDemo...");
        //放行
        chain.doFilter(request,response);
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void destroy() {
    }
}



1.3 Filter执行流程


bf5fd47829702ec6eafd9e2a9a54610b_image-20210823194830074.png


如上图是使用过滤器的流程,我们通过以下问题来研究过滤器的执行流程:


放行后访问对应资源,资源访问完成后,还会回到Filter中吗?


从上图就可以看出肯定 会 回到Filter中


如果回到Filter中,是重头执行还是执行放行后的逻辑呢?


如果是重头执行的话,就意味着 放行前逻辑 会被执行两次,肯定不会这样设计了;所以访问完资源后,会回到 放行后逻辑,执行该部分代码。


通过上述的说明,我们就可以总结Filter的执行流程如下:


500cff1d2f9b183b014bfc6885f98fdd_image-20210823195434581.png


接下来我们通过代码验证一下,在 doFilter() 方法前后都加上输出语句,如下


6be52e077879ab65e5178b87f82b3e21_image-20210823195828596.png


同时在 hello.jsp 页面加上输出语句,如下


4e8a71215ffcef5f7cd4e32776131784_image-20210823200028284.png


执行访问该资源打印的顺序是按照我们标记的标号进行打印的话,说明我们上边总结出来的流程是没有问题的。启动服务器访问 hello.jsp 页面,在控制台打印的内容如下:


f868b0773d206ae340ca90f939ac4138_image-20210823200202153.png


以后我们可以将对请求进行处理的代码放在放行之前进行处理,而如果请求完资源后还要对响应的数据进行处理时可以在放行后进行逻辑处理。


1.4 Filter拦截路径配置


拦截路径表示 Filter 会对请求的哪些资源进行拦截,使用 @WebFilter 注解进行配置。如:@WebFilter("拦截路径")


拦截路径有如下四种配置方式:


拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截

目录拦截:/user/*:访问/user下的所有资源,都会被拦截

后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截

拦截所有:/*:访问所有资源,都会被拦截

通过上面拦截路径的学习,大家会发现拦截路径的配置方式和 Servlet 的请求资源路径配置方式一样,但是表示的含义不同。


1.5 过滤器链


1.5.1 概述


过滤器链是指在一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。


如下图就是一个过滤器链,我们学习过滤器链主要是学习过滤器链执行的流程。


eae1e4831f96b69c426cd3702a97bf18_image-20210823215835812.png


上图中的过滤器链执行是按照以下流程执行:


执行 Filter1 的放行前逻辑代码

执行 Filter1 的放行代码

执行 Filter2 的放行前逻辑代码

执行 Filter2 的放行代码

访问到资源

执行 Filter2 的放行后逻辑代码

执行 Filter1 的放行后逻辑代码

以上流程串起来就像一条链子,故称之为过滤器链。


1.5.2 代码演示


编写第一个过滤器 FilterDemo ,配置成拦截所有资源


@WebFilter("/*")
public class FilterDemo implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //1. 放行前,对 request数据进行处理
        System.out.println("1.FilterDemo...");
        //放行
        chain.doFilter(request,response);
        //2. 放行后,对Response 数据进行处理
        System.out.println("3.FilterDemo...");
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void destroy() {
    }
}



编写第二个过滤器 FilterDemo2 ,配置炒年糕拦截所有资源


@WebFilter("/*")
public class FilterDemo2 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //1. 放行前,对 request数据进行处理
        System.out.println("2.FilterDemo...");
        //放行
        chain.doFilter(request,response);
        //2. 放行后,对Response 数据进行处理
        System.out.println("4.FilterDemo...");
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void destroy() {
    }
}




修改 hello.jsp 页面中脚本的输出语句


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>hello JSP~</h1>
    <%
        System.out.println("3.hello jsp");
    %>
</body>
</html>


启动服务器,在浏览器输入 http://localhost/filter-demo/hello.jsp 进行测试,在控制台打印内容如下


6bc7fb00584198c1d3b4fac92f61da3b_image-20210823221222468.png


从结果可以看到确实是按照我们之前说的执行流程进行执行的。


1.5.3 问题


上面代码中为什么是先执行 FilterDemo ,后执行 FilterDemo2 呢?


我们现在使用的是注解配置Filter,而这种配置方式的优先级是按照过滤器类名(字符串)的自然排序。


比如有如下两个名称的过滤器 : BFilterDemo 和 AFilterDemo 。那一定是 AFilterDemo 过滤器先执行。


1.6 案例


1.6.1 需求


访问服务器资源时,需要先进行登录验证,如果没有登录,则自动跳转到登录页面


1.6.2 分析


我们要实现该功能是在每一个资源里加入登陆状态校验的代码吗?显然是不需要的,只需要写一个 Filter ,在该过滤器中进行登陆状态校验即可。而在该 Filter 中逻辑如下:


84e191b650c22b97c2d67707649d1b1b_image-20210823223214525.png


1.6.3 代码实现


1.6.3.1 创建Filter


在 brand-demo 工程创建 com.sun.web.filter 包,在该下创建名为 LoginFilter 的过滤器


@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
    }
    public void init(FilterConfig config) throws ServletException {
    }
    public void destroy() {
    }
}


1.6.3.2 编写逻辑代码


在 doFilter() 方法中编写登陆状态校验的逻辑代码。


我们首先需要从 session 对象中获取用户信息,但是 ServletRequest 类型的 requset 对象没有获取 session 对象的方法,所以此时需要将 request对象强转成 HttpServletRequest 对象。


HttpServletRequest req = (HttpServletRequest) request;


然后完成以下逻辑


获取Session对象

从Session对象中获取名为 user 的数据

判断获取到的数据是否是 null

如果不是,说明已经登陆,放行

如果是,说明尚未登陆,将提示信息存储到域对象中并跳转到登陆页面

代码如下:


@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
        //1. 判断session中是否有user
        HttpSession session = req.getSession();
        Object user = session.getAttribute("user");
        //2. 判断user是否为null
        if(user != null){
            // 登录过了
            //放行
            chain.doFilter(request, response);
        }else {
            // 没有登陆,存储提示信息,跳转到登录页面
            req.setAttribute("login_msg","您尚未登陆!");
            req.getRequestDispatcher("/login.jsp").forward(req,response);
        }
    }
    public void init(FilterConfig config) throws ServletException {
    }
    public void destroy() {
    }
}



1.6.3.3 测试并抛出问题

在浏览器上输入 http://localhost:8080/brand-demo/ ,可以看到如下页面效果


c02b6036f6e154cc0a4b7fce96651c0e_image-20210823224843179.png


从上面效果可以看出没有登陆确实是跳转到登陆页面了,但是登陆页面为什么展示成这种效果了呢?


1.6.3.4 问题分析及解决


因为登陆页面需要 css/login.css 这个文件进行样式的渲染,下图是登陆页面引入的css文件图解


04e805f5faad96fa6ef673a6c23c78a9_image-20210823225411925.png


而在请求这个css资源时被过滤器拦截,就相当于没有加载到样式文件导致的。解决这个问题,只需要对所以的登陆相关的资源进行放行即可。还有一种情况就是当我没有用户信息时需要进行注册,而注册时也希望被过滤器放行。


综上,我们需要在判断session中是否包含用户信息之前,应该加上对登陆及注册相关资源放行的逻辑处理


//判断访问资源路径是否和登录注册相关
//1,在数组中存储登陆和注册相关的资源路径
String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
//2,获取当前访问的资源路径
String url = req.getRequestURL().toString(); 
//3,遍历数组,获取到每一个需要放行的资源路径
for (String u : urls) {
    //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
    /*
      比如当前访问的资源路径是  /brand-demo/login.jsp
      而字符串 /brand-demo/login.jsp 包含了  字符串 /login.jsp ,所以这个字符串就需要放行
    */
    if(url.contains(u)){
        //找到了,放行
        chain.doFilter(request, response);
        //break;
        return;
    }
}



1.6.3.5 过滤器完整代码


@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
        //判断访问资源路径是否和登录注册相关
        //1,在数组中存储登陆和注册相关的资源路径
        String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
        //2,获取当前访问的资源路径
        String url = req.getRequestURL().toString(); 
        //3,遍历数组,获取到每一个需要放行的资源路径
        for (String u : urls) {
            //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
            /*
                比如当前访问的资源路径是  /brand-demo/login.jsp
                而字符串 /brand-demo/login.jsp 包含了  字符串 /login.jsp ,所以这个字符串就需要放行
            */
            if(url.contains(u)){
                //找到了,放行
                chain.doFilter(request, response);
                //break;
                return;
            }
        }
        //1. 判断session中是否有user
        HttpSession session = req.getSession();
        Object user = session.getAttribute("user");
        //2. 判断user是否为null
        if(user != null){
            // 登录过了
            //放行
            chain.doFilter(request, response);
        }else {
            // 没有登陆,存储提示信息,跳转到登录页面
            req.setAttribute("login_msg","您尚未登陆!");
            req.getRequestDispatcher("/login.jsp").forward(req,response);
        }
    }
    public void init(FilterConfig config) throws ServletException {
    }
    public void destroy() {
    }
}


相关文章
|
17天前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
84 43
Java学习十六—掌握注解:让编程更简单
|
2天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
25天前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
25 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
10天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
26天前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
35 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
24天前
|
存储 算法 Java
带你学习java的数组军队列
带你学习java的数组军队列
33 0
|
27天前
|
Java 大数据 开发工具
java学习——环境准备(1)
java学习——环境准备(1)
41 0
|
12天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
91 38
|
9天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####