【Java Web编程 十】深入理解Servlet过滤器

简介: 【Java Web编程 十】深入理解Servlet过滤器

前几篇Blog主要在学习Servlet组件,包括Servlet的基本概念、注解使用方式、生命周期以及Servlet的常用对象HttpServletRequest、HttpServletResponse、ServletConfig、ServletContext,之后又了解了常用于会话追踪的HttpSession对象和Cookie对象。算是对Servlet有了个全局的认知,其实Java Web有三大组件:Servlet 程序、Listener 监听器、Filter 过滤器,今天这篇Blog则着重介绍下过滤器

过滤器基本概念

聊组件之前一定要聊聊为什么有这个组件,我们知道为什么需要用Servlet,因为Servlet提供了一系列的对象来处理用户的请求和返回给用户响应,核心功能就是用来完成一次连接的逻辑处理,而一些通用的处理,与各请求无关的水平逻辑就需要过滤器来处理。

过滤器作用

那么过滤器有什么作用呢?就像它的名字一样,它是过滤器,用来拦截请求和过滤响应的,具体一点,主要用来完成一些通用的操作:登录检查(判断用户登录状态)、编码过滤、操作记录、事务管理、图像转换过滤、认证过滤、审核过滤、加密过滤等等,有点像AOP切面的操作,例如我们下面提到的登录检查:

创建过滤器

用IDEA可以很轻松的创建一个过滤器出来,按照如下步骤:

创建完成后可以看到已经生成了过滤器的代码模板了:

package com.example.myfirstweb.controller; /**
 * * @Name ${NAME}
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/7/27
 */
import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebFilter(filterName = "ServletFilter")
public class ServletFilter implements Filter {
    @Override
    public void init(FilterConfig config) throws ServletException {
    }
    @Override
    public void destroy() {
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        chain.doFilter(request, response);
    }
}

可以看到使用了WebFilter的注解,这样就不需要通过web.xml进行配置了:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package javax.servlet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.servlet.DispatcherType;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebFilter {
    String description() default "";
    String displayName() default "";
    WebInitParam[] initParams() default {};
    String filterName() default "";
    String smallIcon() default "";
    String largeIcon() default "";
    String[] servletNames() default {};
    String[] value() default {};
    String[] urlPatterns() default {};
    DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};
    boolean asyncSupported() default false;
}

注解的一些配置说明如下:

过滤器常用方法

过滤器的常用方法有三个,主要贯穿其生命周期,包含如下几个方法:

  1. 构造器方法和init 初始化方法,在 web 工程启动的时候执行,进行Filter 已经创建
  2. doFilter 过滤方法,每次拦截到请求,就会执行
  3. destroy 销毁方法,停止 web 工程的时候,就会执行,所以停止 web 工程,也会销毁 Filter 过滤器

我们最常用的就是doFilter方法,通常在里面重写就行了,整体的方法继承结构如下;

过滤器实现登录拦截

我们用过滤器来实现个拦截登录,实现起来很简单,就是所有的jsp页面请求都需要被过滤器给拦截到然后判断是否登录成功,如果登录成功了则允许访问,如果没有登录成功则跳转到登录页面,同时记忆当前请求的页面,登录成功后自动跳转到请求的页面。

JSP页面

我们先预置三个JSP页面用来测试,其中一个为login.jsp也就是登录表单的提交页面:

index.jsp页面

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*" errorPage="jsp/error.jsp" %>
<%@ include file="jsp/top.jsp" %>
<!DOCTYPE html>
<html>
<head>
    <title>JSP - Hello World</title>
    <%!
        int number = 0;// 声明全局变量
        int count() {
            number++;//number 自增
            return number;
        }%>
</head>
<h1><%= "Hello World!" %>
</h1>
<br/>
<body>
<%
    String print = "我是脚本语句内容";
    List<String> strList = new ArrayList<>();
    strList.add("脚本语句1");
    strList.add("脚本语句2");
    strList.add("脚本语句3");
    //  strList.add("脚本语句4");  //这是一个Java注释,用来注释Java代码,查看源码时不可见
%>
<a href="hello-servlet">我的第一个JavaWeb项目</a> <!-- 这是一个html注释,可见<%=new Date().getTime() %> -->
<br/>
<%--这是一个JSP注释,查看源码时不可见--%>
声明语句示例:这是第 <%=count()%> 次访问该页面
<br/>
表达式语句示例: <%="我是一个表达式语句"%>
<br/>
</body>
</html>

look.jsp页面

<%--
  Created by IntelliJ IDEA.
  User: tianmaolin
  Date: 2021/7/28
  Time: 11:21 上午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录后可见哦</title>
</head>
<body>
登录后可见哦
</body>
</html>

login.jsp页面

<%--
  Created by IntelliJ IDEA.
  User: tianmaolin
  Date: 2021/7/27
  Time: 9:39 下午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<body>
<%="进入登录页面"%>
<form action="myServlet" method="post">
    用户名 <input type="text" name="username"><br>
    密码   <input type="text" name="password"><br>
    <input type="submit" value="submit">
</form>
</body>
</body>
</html>

Servlet页面

当然要有处理登录信息的Servlet代码,用来塞入用户的信息和用户登录状态信息:

package com.example.myfirstweb.controller; /**
 * * @Name ${NAME}
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/7/19
 */
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet(name = "myServlet", value = "/myServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        response.getWriter().println("进入用户登录页面");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        response.getWriter().println("用户名 " + username + "密码 " + password);
        //满足如下条件则认为登录成功
        if (username.equals("admin") && password.equals("root")) {
            //重置登录session
            HttpSession session = request.getSession();
            session.setAttribute("username", username);
            session.setAttribute("password", password);
            session.setAttribute("loginStatus", "登录成功");
            response.getWriter().println("用户登录成功");
            //获取登录前的url做跳转
            String url = (String) session.getAttribute("URL");
            System.out.println(url);
            request.getRequestDispatcher(url).forward(request, response);
        }
    }
}

Servlet过滤器

当然最关键的就是我们写的Servlet过滤器了,每一个jsp请求到达都会被该过滤器拦截然后进行相关操作,例如判断是否登录成功,没有登录成功一律转到登录的jsp页面:

package com.example.myfirstweb.filter; /**
 * * @Name ${NAME}
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/7/27
 */
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter(filterName = "ServletFilter",urlPatterns= "*.jsp")
public class ServletFilter implements Filter {
    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println("执行过滤器的init()方法");
    }
    @Override
    public void destroy() {
        System.out.println("执行过滤器的destroy()方法");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("执行过滤器的doFilter()方法");
        HttpServletRequest requestServlet = (HttpServletRequest) request;
        HttpServletResponse responseServlet = (HttpServletResponse) response;
        HttpSession session=((HttpServletRequest) request).getSession();
        String loginStatus=(String)session.getAttribute("loginStatus");
        String url=requestServlet.getRequestURI();
        System.out.println("url链接"+url);
        //登录页面无需跳转登录页面,否则会一直报重定向的错误
        if(url.endsWith("login.jsp")){
            System.out.println("访问的登录页面login.jsp");
            chain.doFilter(request, response);
            return;
        }
        if(loginStatus==null || !loginStatus.equals("登录成功")){
            System.out.println("未登录,跳转到登录页面");
            String urlget=url.substring(url.lastIndexOf("/")+1,url.length());
            session.setAttribute("URL",urlget);
            responseServlet.sendRedirect("login.jsp");
            return;
        }else{
            chain.doFilter(request, response);
        }
    }
}

实现效果

当我们启动服务器访问index.jsp或者look.jsp页面时请求会被拦截,然后进行登录判断,只有登录成功的才会允许继续执行:

输入用户名和密码后自动依据当前请求的链接进行请求转发:

过滤器链

Filter 过滤器,也就是Chain 链,链条的意思,FilterChain 就是过滤器链,是用来描述多个过滤器如何一起工作。

过滤器链有如下特征:

  1. 所有filter和目标资源默认都执行在同一个线程中
  2. 多个filter共同执行时,他们都是用同一个Request对象

多个filter组成的过滤器链,在WebFilter注解使用情况下网上很多人说依据类名字典顺序执行,实际操作后发现并非如此,并且官方并没有给出具体的排序,所以还是最好配置web.xml里。

我们使用过滤器链来观察下:

第一个过滤器

Filter0过滤器

package com.example.myfirstweb.filter; /**
 * * @Name ${NAME}
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/7/29
 */
import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebFilter(filterName = "Filter0", urlPatterns = "/index.jsp")
public class Filter0 implements Filter {
    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println("执行过滤器Filter0的init()方法");
    }
    @Override
    public void destroy() {
        System.out.println("执行过滤器Filter0的destroy()方法");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("我是过滤器Filter0的前置代码");
        chain.doFilter(request, response);
        System.out.println("我是过滤器Filter0的后置代码");
    }
}

第二个过滤器

Filter1过滤器

package com.example.myfirstweb.filter; /**
 * * @Name ${NAME}
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/7/29
 */
import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebFilter(filterName = "Filter1", urlPatterns = "/index.jsp")
public class Filter1 implements Filter {
    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println("执行过滤器Filter1的init()方法");
    }
    @Override
    public void destroy() {
        System.out.println("执行过滤器Filter1的destroy()方法");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("我是过滤器Filter1的前置代码");
        chain.doFilter(request, response);
        System.out.println("我是过滤器Filter1的后置代码");
    }
}

Web.xml配置

配置顺序在先的先执行:

<filter-mapping>
        <filter-name>Filter0</filter-name>
        <url-pattern>/index.jsp</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>Filter1</filter-name>
        <url-pattern>/index.jsp</url-pattern>
    </filter-mapping>

打印结果展示

请求index.jsp页面后分别被过滤器链上的过滤器执行:

总结一下

过滤器有点像一个切面,它主要是对通用的一些逻辑进行统一处理,例如编码转换,日志输出,登录判断,权限判断等。而且可以使用过滤器链来完成一系列逻辑,注意过滤器链使用时要在web.xml中配置执行顺序

相关文章
|
2月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
2月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
226 1
|
2月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
161 6
|
2月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
161 0
|
3月前
|
SQL Java 数据库
2025 年 Java 从零基础小白到编程高手的详细学习路线攻略
2025年Java学习路线涵盖基础语法、面向对象、数据库、JavaWeb、Spring全家桶、分布式、云原生与高并发技术,结合实战项目与源码分析,助力零基础学员系统掌握Java开发技能,从入门到精通,全面提升竞争力,顺利进阶编程高手。
679 1
|
3月前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
457 100
|
3月前
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
397 0
|
3月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
394 1
|
3月前
|
NoSQL Java 关系型数据库
超全 Java 学习路线,帮你系统掌握编程的超详细 Java 学习路线
本文为超全Java学习路线,涵盖基础语法、面向对象编程、数据结构与算法、多线程、JVM原理、主流框架(如Spring Boot)、数据库(MySQL、Redis)及项目实战等内容,助力从零基础到企业级开发高手的进阶之路。
329 1
|
3月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
262 16