【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中配置执行顺序

相关文章
|
25天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
27天前
|
安全 Java UED
深入浅出Java多线程编程
【10月更文挑战第40天】在Java的世界中,多线程是提升应用性能和响应能力的关键。本文将通过浅显易懂的方式介绍Java中的多线程编程,从基础概念到高级特性,再到实际应用案例,带你一步步深入了解如何在Java中高效地使用多线程。文章不仅涵盖了理论知识,还提供了实用的代码示例,帮助你在实际开发中更好地应用多线程技术。
43 5
|
1月前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
117 3
|
13天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
16天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
16天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
10天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
10天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
30 3
|
15天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
34 2
|
20天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界里,异常是程序运行中不可忽视的“惊喜”。它们可能突如其来,也可能悄无声息地潜伏。掌握异常处理的艺术,意味着你能够优雅地面对程序的不完美,并确保它即使在风雨飘摇中也能继续航行。本文将引导你理解Java异常的本质,探索捕获和处理这些异常的方法,并最终学会如何利用自定义异常为你的代码增添力量。