前几篇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; }
注解的一些配置说明如下:
过滤器常用方法
过滤器的常用方法有三个,主要贯穿其生命周期,包含如下几个方法:
- 构造器方法和init 初始化方法,在 web 工程启动的时候执行,进行Filter 已经创建
- doFilter 过滤方法,每次拦截到请求,就会执行
- 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 就是过滤器链,是用来描述多个过滤器如何一起工作。
过滤器链有如下特征:
- 所有filter和目标资源默认都执行在同一个线程中
- 多个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中配置执行顺序