Filter&Listener
学习目标
- 了解Filter
- 能够使用 Filter 完成登陆状态校验功能
学习内容
- 了解Filter
- 能够使用 Filter 完成登陆状态校验功能
学习产出
1 Filter
1.1 Filter概述
Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
可以将每次访问Servlet都需要进行的步骤在Filter中写。
比如会话技术中的案例,当没有登录时也可以去查看全部的数据。
需要每次查看都要通过过滤器进行判断是否登录,登录了才可以查看。
我们就可以放在过滤器中进行实现。这个就是权限控制,以后我们还会进行细粒度权限控制。过滤器还可以做 统一编码处理
、 敏感字符处理
等
1.2 Filter快速入门
进行 Filter
开发分成以下三步实现
-
定义类,实现 Filter接口,并重写其所有方法
-
配置Filter拦截资源的路径:在类上定义
@WebFilter
注解。而注解的value
属性值/*
表示拦截所有的资源
-
在doFilter方法中输出一句话,并放行
写个案例进行测试
创建一个maven工程,配置好基本的pom.xml文件,webapp等。.
创建一个新的jsp文件,创建一个FilterDemo文件 -
.在demo中写sout代码,但是不放行,重启服务器发现,IDEA控制台可以看到sout输出的部分,但是在网页中访问jsp不显示
-
在demo中放行,在重启服务器进行访问jsp可以在网页中看到里面的内容
hello.jsp
<%--
Created by IntelliJ IDEA.
User: zpd
Date: 2022/6/6
Time: 19:06
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>hello</title>
</head>
<body>
<h1>Hello World Java!</h1>
</body>
</html>
FilterDemo.java
package com.zpd.web.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Description
* @Author zpd
* @Date 2022/6/6
*/
@WebFilter("/*")
public class FilterDemo implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("FilterDemo...");
//放行
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
1.3 Filter执行流程
如上图是使用过滤器的流程,我们通过以下问题来研究过滤器的执行流程:
-
放行后访问对应资源,资源访问完成后,还会回到Filter中吗?
从上图就可以看出肯定 会 回到Filter中
-
如果回到Filter中,是重头执行还是执行放行后的逻辑呢?
如果是重头执行的话,就意味着
放行前逻辑
会被执行两次,肯定不会这样设计了;所以访问完资源后,会回到放行后逻辑
,执行该部分代码。
通过上述的说明,我们就可以总结Filter的执行流程如下:
接下来我们通过代码验证一下, 在 doFilter()
方法前后都加上输出语句,如下,同时在 hello.jsp
页面加上输出语句。
doFile() { }
//放行前,对request进行处理
System.out.println("1 FilterDemo...");
//放行
filterChain.doFilter(servletRequest,servletResponse);
//放行后,对response 数据处理
System.out.println("3 FilterDemo...");
hello.jsp
<%
System.out.println("2 FilterDemo...");
%>
执行访问该资源打印的顺序是按照我们标记的标号进行打印的话,说明我们上边总结出来的流程是没有问题的。启动服务器访问 hello.jsp
页面,在控制台打印的内容如下:
1.5 Filter使用细节
1.5.1 Filter拦截路径配置
拦截路径表示 Filter 会对请求的哪些资源进行拦截,使用 @WebFilter
注解进行配置。如:@WebFilter("拦截路径")
拦截路径有如下四种配置方式:
- 拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截
- 目录拦截:/user/*:访问/user下的所有资源,都会被拦截
- 后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截
- 拦截所有:/*:访问所有资源,都会被拦截
通过上面拦截路径的学习,大家会发现拦截路径的配置方式和 Servlet
的请求资源路径配置方式一样,但是表示的含义不同。
1.5.2 过滤器链
过滤器链是指在一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。
上图中的过滤器链执行是按照以下流程执行:
- 执行
Filter1
的放行前逻辑代码 - 执行
Filter1
的放行代码 - 执行
Filter2
的放行前逻辑代码 - 执行
Filter2
的放行代码 - 访问到资源
- 执行
Filter2
的放行后逻辑代码 - 执行
Filter1
的放行后逻辑代码
以上流程串起来就像一条链子,故称之为过滤器链。
案例测试
修改FilterDemo和FilterDemo2的代码,让两个过滤器都是对hello.jsp进行过滤,并重新访问hello.jsp
FilterDemo
package com.zpd.web.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Description
* @Author zpd
* @Date 2022/6/6
*/
@WebFilter("/*")
public class FilterDemo implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//放行前,对request进行处理
System.out.println("1 FilterDemo...");
//放行
filterChain.doFilter(servletRequest,servletResponse);
//放行后,对response 数据处理
System.out.println("5 FilterDemo...");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
FilterDemo2
package com.zpd.web.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Description
* @Author zpd
* @Date 2022/6/6
*/
//@WebFilter("/hello.jsp")
@WebFilter("/*")
public class FilterDemo2 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//放行前,对request进行处理
System.out.println("2 FilterDemo...");
//放行
filterChain.doFilter(servletRequest,servletResponse);
//放行后,对response 数据处理
System.out.println("4 FilterDemo...");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
从结果可以看到确实是按照之前说的执行流程进行执行的。
但是为什么是先执行 FilterDemo
,后执行 FilterDemo2
呢?
我们现在使用的是注解配置Filter,而这种配置方式的优先级是按照过滤器类名(字符串)的自然排序。
比如有如下两个名称的过滤器 : BFilterDemo
和 AFilterDemo
。那一定是 AFilterDemo
过滤器先执行。
1.6 登录案例
创建LoginFilter,在web下创建Filter文件夹,并创建LoginFilter.
在LoginFilter对登录进行验证
- 首先考虑的是判断是否登录。
- 然后是从Session中获取user
- 判断user是否为空
- 不为空:放行
- 空:返回
login.jsp
,并显示尚未登录
进行验证发现无法跳转界面,并且将浏览器内存清除后,存css无用。
说明Filter将所以的界面包括css、imgs都过滤了。
需要进行优化
在判断user前先判断访问的资源路径是否和登录和注册有关。有关则直接放行,并退出。
LoginFilter.java
package com.zpd.web.filter; /**
* @Description
* @Author zpd
* @Date 2022/6/8
*/
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/*")
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
//将ServletRequest类型的request强转为HttpServletRequest类型的
HttpServletRequest req = (HttpServletRequest) request;
//判断访问的资源路径,是否和登录注册相关
String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
//获取当前访问的资源路径
String url = req.getRequestURL().toString();
System.out.println(url);
for (String u : urls) {
if (url.contains(u)){
chain.doFilter(request, response);
return;
}
}
//判断Session中是否有user对象、
//获取Session
HttpSession session = req.getSession();
Object user = session.getAttribute("user");
if (user!=null){
//放行
chain.doFilter(request, response);
}else {
request.setAttribute("loginError", "尚未登录");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
}
2 Listener
2.1 概述
-
Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
-
监听器可以监听就是在
application
,session
,request
三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。request 和 session 我们学习过。而
application
是ServletContext
类型的对象。ServletContext
代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象。
2.2 分类
JavaWeb 提供了8个监听器:
这里面只有 ServletContextListener
这个监听器后期我们会接触到,ServletContextListener
是用来监听 ServletContext
对象的创建和销毁。
ServletContextListener
接口中有以下两个方法
void contextInitialized(ServletContextEvent sce)
:ServletContext
对象被创建了会自动执行的方法void contextDestroyed(ServletContextEvent sce)
:ServletContext
对象被销毁时会自动执行的方法
2.3 代码演示
我们只演示一下 ServletContextListener
监听器
- 定义一个类,实现
ServletContextListener
接口 - 重写所有的抽象方法
- 使用
@WebListener
进行配置
ContextLoaderListener.java
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//加载资源
System.out.println("ContextLoaderListener...");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
//释放资源
}
}
启动服务器,就可以在启动的日志信息中看到 contextInitialized()
方法输出的内容,同时也说明了 ServletContext
对象在服务器启动的时候被创建了。