WEB阶段6:过滤器&监听器&全局字符修改案例&用户权限过滤案例&装饰者模式过滤敏感词汇&统计当前网站在线人数

tech2024-07-14  58

过滤器&监听器&全局字符修改案例&用户权限过滤案例&装饰者模式过滤敏感词汇&统计当前网站在线人数

回顾

JSP的页面脚本元素 组成部分语法格式JSP代码片段<% Java代码 %>JSP声明<%! 声明全局变量 %>JSP脚本表达式<%= 变量值 %>注释<%-- --%> 2. EL表达式如何获取不同类型的数据 EL表达式获取不同数据说明获取JavaBean的属性值${对象名.属性名}获取数组和List中的值${集合或数组[索引]}获取Map中的值${map.键} 或 ${map[“键”]} 判断标签 <c:if test="${判断条件}"> </c:if> 多分支标签 <c:choose> <c:when test="${判断条件}"> </c:when> <c:when test="${判断条件}"> </c:when> <c:otherwise> </c:otherwise> </c:choose> 遍历标签 forEach 属性名属 性 描 述var每个要遍历的元素,放在页面域中varStatus变量的状态对象,包含了四个属性:index, count, first, lastitems要遍历的集合或数组,放在作用域中begin从哪个元素开始遍历end到哪个元素结束step步长,每次跨几个元素 什么是MVC MVC描述Java Web的实现技术MModel:模型JavaBean, Service, DaoVView:视图JSP, JSTL, ELCController:控制器Servlet1. 获取用户提交的参数值2. 调用业务层的方法3. 控制页面的跳转

学习目标

过滤器

能够说出过滤器的作用能够编写过滤器能够说出过滤器生命周期相关方法能够根据过滤路径判断指定的过滤器是否起作用能够说出什么是过滤器链能够编写过滤器解决全局乱码

监听器

能够说出监听器的作用能够使用ServletContextListener监听器

学习内容

1. 过滤器的基本概念

目标

过滤器的概念

JavaWeb的三种组件

过滤器的使用场景

Java Web的三种基本组件

组件作用Servlet运行在Web容器中Java程序,在MVC中做的控制器使用,生成动态网页。过滤器Filter用来处理一些公共的,通用的功能1. 拦截用户的请求2. 修改用户的请求和响应监听器Listener对作用域进行监听1. 监听作用域的创建和销毁2. 监听作用域中属性的变化

过滤器所处的位置

过滤器的使用场景:

可以集中处理汉字乱码的问题,把处理乱码的代码写在过滤器中,让所有通过这个过滤器的请求都没有乱码的问题

用户登录权限的判断,只需要在过滤器中判断请求,如果登录了就放行,没有登录就拦截。

可以对用户发送的内容进行过滤,让最终的内容发生变化或拦截

小结

JavaWeb的三种组件 ServletFilterListener 过滤器的使用场景:对请求和响应进行修改或拦截

2. 案例:编写第1个过滤器【重点】

目标

过滤器的开发步骤

编写第1个过滤器

过滤器的演示案例:

需求

Web资源是:HelloServlet过滤器:HelloFilter

创建一个过滤器HelloFilter,在运行HelloServlet前和后分别输出一句话,在HelloServlet中也输出一句话,观察控制台的运行效果。

执行效果:

Servlet的代码

package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/demo1") public class Demo1HelloServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("到达Web资源:Servlet"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }

使用注解的方式

package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; /** * 创建过滤器的步骤: * 1. 创建一个类,实现javax.servlet.Filter接口 (注意不要选错) * 2. 要重写接口中所有的方法,其中doFilter方法就是执行过滤功能的方法 * 3. 要在web.xml中配置过滤器的过滤地址或使用@WebFilter注解来配置 */ @WebFilter("/demo1") //可以过滤所有的资源或过滤某些指定的资源,这是它过滤的地址,不是访问地址 public class Demo1HelloFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * 执行过滤的功能 * @param req 请求对象,它是HttpServletRequest的父接口,其实是同一个对象 * @param resp 响应对象,它是HttpServletResponse的父接口,其实是同一个对象 * @param chain 过滤器链 */ @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { System.out.println("过滤器:请求的时候执行"); //调用过滤器链的方法,如果执行这句话,请求被放行,到达web资源;如果没有执行,请求就被拦截 chain.doFilter(req,resp); System.out.println("过滤器:响应的时候执行"); } @Override public void destroy() { } }

使用配置文件的方式

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--使用配置的方式指定过滤器--> <filter> <!--过滤器的名字--> <filter-name>demo1</filter-name> <!--过滤器的类全名--> <filter-class>com.itheima.filter.Demo1HelloFilter</filter-class> </filter> <!--指定过滤器的过滤地址--> <filter-mapping> <!--与上面的名字相同--> <filter-name>demo1</filter-name> <!--这是过滤地址,不是访问地址,必须以/开头--> <url-pattern>/demo1</url-pattern> </filter-mapping> </web-app>

小结

过滤器的编写要实现哪个接口?

javax.servlet.Filter

过滤的方法是哪个?

doFilter(请求,响应,过滤链)

@WebFilter注解

@WebFilter注解属性说明filterName过滤器的名字urlPatterns过滤的地址:是一个字符串的数组,可以指定多个value同上

3. 过滤器的执行流程

目标

过滤器的执行流程

过滤器的执行流程

过滤器也是运行在Web容器中

过滤器的执行流程如下:

用户发送请求,请求的是Web资源。如果请求的资源访问地址与过滤器过滤的地址匹配,就会执行过滤器执行过滤器中doFilter方法在doFilter中再调用一个chain.doFilter()方法放行,如果没有执行这句话就是拦截如果放行,就会到达web资源响应回来的时候还会再次经过过滤器,并且执行doFilter()放行后面的代码返回到浏览器端

4. 过滤器的生命周期【重点】

目标

过滤器的生命周期有哪些方法

过滤器加载的时机:

回顾:以前Servlet是什么时间加载的?

用户第一次访问的时候加载

Filter什么时候加载呢?

因为过滤器要拦截其它的资源,所以必须比其它资源更早实例化。在服务器启动的时候就加载了。

生命周期的方法

Filter接口中的方法作用和执行次数void init(FilterConfig filterConfig)初始化的时候执行,执行1次void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)只要匹配过滤的地址,每次请求都会执行,执行多次public void destroy()服务器关闭的时候,销毁执行1次

示例:生命周期的过程

执行效果

案例代码

package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; import java.sql.Timestamp; /* filterName:表示过滤器的名字,不要出现同名 urlPatterns:过滤的地址 */ @WebFilter(filterName = "Demo2LifeCycleFilter", urlPatterns = "/demo1") public class Demo2LifeCycleFilter implements Filter { //初始化的方法,在tomcat启动的时候,执行1次 public void init(FilterConfig config) throws ServletException { System.out.println(new Timestamp(System.currentTimeMillis()) + " 初始化过滤器"); } //每次请求都会执行 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println(new Timestamp(System.currentTimeMillis()) + "过滤器:请求的时候执行"); //放行 chain.doFilter(req, resp); System.out.println(new Timestamp(System.currentTimeMillis()) + "过滤器:响应的时候执行"); } //销毁的时候执行1次 public void destroy() { System.out.println(new Timestamp(System.currentTimeMillis()) + "过滤器销毁"); } }

小结

过滤器什么时候执行初始化?服务器启动过滤的方法执行多少次?多次过滤器什么时候销毁?服务器关闭的时候

5. FilterConfig接口

目标

学习FilterConfig接口的方法

方法

使用配置文件的方式中

FilterConfig接口中的方法功能String getInitParameter(“参数名”)通过参数名获取配置文件中初始的参数值Enumeration<String> getInitParameterNames()获取配置文件中所有的初始参数名

代码

过滤器代码

package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; import java.util.Enumeration; /** * 读取配置参数,所以使用配置的方式 */ public class Demo3ConfigFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { //放行 chain.doFilter(req, resp); } //过滤器的配置对象,通过init方法传递进来,可以在方法中直接使用 public void init(FilterConfig config) throws ServletException { //通过参数名读取一个初始的参数值 String user = config.getInitParameter("user"); System.out.println("参数:" + user); //读取所有的初始参数名字 Enumeration<String> parameterNames = config.getInitParameterNames(); //遍历 while (parameterNames.hasMoreElements()) { String name = parameterNames.nextElement(); //获取名字 String value = config.getInitParameter(name); //通过名字获取值 System.out.println("初始参数名:" + name + ",值:" + value); } } }

web.xml

<filter> <filter-name>demo3</filter-name> <filter-class>com.itheima.filter.Demo3ConfigFilter</filter-class> <!--添加初始的参数--> <init-param> <param-name>user</param-name> <param-value>Rose</param-value> </init-param> <init-param> <param-name>age</param-name> <param-value>20</param-value> </init-param> </filter> <filter-mapping> <filter-name>demo3</filter-name> <url-pattern>/demo1</url-pattern> </filter-mapping>

效果

只要启动服务器就被初始化,就执行了init()方法中代码

参数:Rose 初始参数名:user,值:Rose 初始参数名:age,值:20

小结

FilterConfig接口中有以下两个方法:

getInitParameter() 通过初始的参数名,获取参数值 getInitParameterNames() 获取所有的初始参数名字

6. 过滤器映射的访问路径

目标

理解过滤器的映射路径的写法

Servlet中与过滤器中映射路径的区别

Servlet:路径就是它的访问地址

@WebServlet("/demo1")

Filter:不是它的访问地址,是它的过滤地址

@WebFilter("/demo1")

疑问:浏览器访问目标资源的路径,如果目标地址不存在,过滤器会不会运行?

只要匹配过滤地址,无论目标资源是否存在,都会执行过滤器

过滤地址编写方式

匹配方式匹配哪些资源示例以/开头精确匹配,访问的资源地址与过滤的地址完全一样/demo1目录匹配,过滤的是某个目录下所有的资源/admin/*过滤所有的资源,整个web模块下所有的资源都会过滤/*以扩展名结尾匹配指定的扩展名就会被过滤*.do

疑问:以/开头的匹配模式和以扩展名结尾的配置,同时出现会怎样?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pJnxNvJP-1599131780590)(assets/image-20200903101959187.png)]

Caused by: java.lang.IllegalArgumentException: 过滤器映射中的<url-pattern> [/*.do] 无效 结论:不能同时出现/开头和扩展名结尾,否则会导致tomcat加载这个模块失败,所有的资源都不能正常访问

过滤多个地址的写法

因为它的过滤地址是一个字符串的数组,可以指定多个过滤的地址

过滤器匹配多个地址说明@WebFilter({"/demo1","/demo2"})同时过滤demo1和demo2过滤admin目录下所有的资源和所有的JSP页面过滤的地址是所有过滤地址的并集,不是交集

小结:根据过滤路径判断指定的过滤器是否起作用

浏览器的访问地址过滤器的配置是否起作用http://localhost:8080/项目地址/aaa/*是http://localhost:8080/项目地址/aaa/aaa是,精确匹配http://localhost:8080/项目地址/aaa.do*.do是,扩展名匹配http://localhost:8080/项目地址/aaa/bbb/aaa/*是,目录匹配http://localhost:8080/项目地址/bbb.do/*.do否,错误http://localhost:8080/项目地址/aaa/bbb.action/aaa/*.action否,错误

7. 过滤器的三种拦截方式

目标

过滤器常用的两种拦截方式

默认的拦截方式

在默认的情况下只有直接在地址栏上输入的访问地址,才会经过过滤器。这种拦截方式称为REQUEST拦截

案例

1). 在index.jsp转发到HelloServlet

2). 过滤器的配置

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>首页</title> </head> <body> <%-- 转发 --%> <%--<jsp:forward page="/demo1"></jsp:forward>--%> <%--包含页面--%> <jsp:include page="/demo1"/> </body> </html>

配置方式1:注解的方式:

/* filterName:表示过滤器的名字,不要出现同名 urlPatterns:过滤的地址 dispatcherTypes: 指定拦截方式 DispatcherType.FORWARD 拦截转发 DispatcherType.REQUEST 拦截直接在浏览器上输入的请求 DispatcherType.INCLUDE 拦截包含的页面 */ @WebFilter(filterName = "Demo2LifeCycleFilter", urlPatterns = "/demo1", dispatcherTypes = {DispatcherType.FORWARD, DispatcherType.REQUEST, DispatcherType.INCLUDE})

配置方式2:web.xml文件

<!--使用配置的方式指定过滤器--> <filter> <!--过滤器的名字--> <filter-name>demo1</filter-name> <!--过滤器的类全名--> <filter-class>com.itheima.filter.Demo1HelloFilter</filter-class> </filter> <!--指定过滤器的过滤地址--> <filter-mapping> <!--与上面的名字相同--> <filter-name>demo1</filter-name> <!--这是过滤地址,不是访问地址,必须以/开头--> <url-pattern>/demo1</url-pattern> <!--拦截方式--> <dispatcher>FORWARD</dispatcher> <dispatcher>REQUEST</dispatcher> <dispatcher>INCLUDE</dispatcher> </filter-mapping>

小结:过滤器的拦截类型

过滤类型作用REQUEST对正常的请求进行拦截FORWARD对转发进行拦截INCLUDE对包含进行拦截

8. 案例:使用过滤器过滤全局汉字乱码问题【重点】

目标

编写过滤器,过滤所有Servlet中使用POST方法提交的汉字的编码。

分析

开发步骤

有2个Servlet,一个是LoginServlet登录,一个是RegisterServlet注册有2个JSP页面,1个是login.jsp,有表单,登录名。1个register.jsp,有表单,有注册的名字。都使用POST提交用户名使用汉字提交。使用过滤器,对所有的Servlet的POST方法进行过滤。在没有使用过滤器之前,每个Servlet必须加上汉字编码:request.setCharacterEncoding(字符集); 字符集与网页的编码要一致

代码

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <h2>登录页面</h2> <form action="login" method="post"> 登录名:<input type="text" name="user"><br> <input type="submit" value="登录"> </form> </body> </html>

register.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户注册</title> </head> <body> <h2>用户注册</h2> <form action="register" method="post"> 注册名:<input type="text" name="name"><br> <input type="submit" value="注册"> </form> </body> </html>

LoginServlet.java

package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); //获取提交的用户名 String user = request.getParameter("user"); out.print("登录名是:" + user); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }

RegisterServlet.java

package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/register") public class RegisterServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); //获取参数 String name = request.getParameter("name"); out.print("注册的名字:" + name); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }

EncodeFilter.java

package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * 设置为过滤所有的Servlet */ @WebFilter(filterName = "CharacterEncodingFilter", urlPatterns = "/*") public class CharacterEncodingFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { //1.判断是否是POST方法 HttpServletRequest request = (HttpServletRequest) req; //2.这里POST是大写 if (request.getMethod().equals("POST")) { //3.对POST方法进行编码 request.setCharacterEncoding("utf-8"); } //4.要放行 chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException { } }

小结

编写一个过滤器就可以对所有的Servlet进行汉字编码

使用哪个方法编码:request.setCharacterEncoding(“utf-8”)过滤的地址: /*放行使用哪个方法:chain.doFilter(请求,响应)

9. 案例:用户权限的过滤器【重点】

目标:

使用过滤器进行权限的控制,实现正确的访问

add.jsp 添加数据,需要登录才可访问update.jsp 修改数据,需要登录才可访问list.jsp 查询数据,不用登录login.jsp 登录页面

项目结构

 

流程

实现步骤:

在Web下创建4个页面 login.jsp上使用${msg},显示信息。创建LoginServlet, 判断用户名密码是否正确,如果正确,则在会话域中保存用户信息。登录成功跳转到add.jsp,登录失败则在域中写入登录失败的信息,并且跳转到login.jsp。使用过滤器解决:创建AuthorityFilter 得到HttpServletRequest、HttpSession对象如果会话中没有用户信息,则转到登录页面,并return。否则继续访问后续的Web资源

案例代码:

登录页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录页面</title> </head> <body> <h2>用户登录</h2> <form action="login" method="post"> 用户名:<input type="text" name="username"> <span id="info">${msg}</span><br> 密码:<input type="password" name="password"><br> <input type="submit" value="登录"> </form> </body> </html>

LoginServlet

package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/login") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.获取用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); //2.判断用户名和密码是否正确 if ("admin".equals(username) && "123".equals(password)) { //3.如果正确,就把用户的信息放在会话域中 HttpSession session = request.getSession(); session.setAttribute("username", username); //4.重定向到add.jsp (添加页面) response.sendRedirect(request.getContextPath() + "/admin/add.jsp"); } //5.登录失败,向请求域中添加信息,转发到JSP页面上显示 else { request.setAttribute("msg", "用户名或密码错误"); //这里必须使用转发,因为要保留请求域中数据 request.getRequestDispatcher("/login.jsp").forward(request, response); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }

AuthorityFilter

package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; /** * 注:这个过滤地址不能指定为/*所有,必须要修改为/admin/* * 没有登录进行拦截,将用户重定向到登录页面 */ @WebFilter(filterName = "AuthorityFilter", urlPatterns = "/admin/*") public class AuthorityFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { //1. 过滤器中判断会话域是否有用户的信息 //将父接口转成子接口 HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; //获取会话对象 HttpSession session = request.getSession(); String username = (String) session.getAttribute("username"); if (username != null) { //2. 如果有表示已经登录,放行 chain.doFilter(req, resp); } else { //3. 如果没有就不能访问,进行拦截 response.sendRedirect(request.getContextPath() + "/login.jsp"); } } public void init(FilterConfig config) throws ServletException { } }

小结

过滤的地址是:/admin/*注:过滤器中使用的是请求对象和响应对象都是父接口,如果要使用子接口中方法,必须进行强转

10. 过滤器链FilterChain的使用

目标

什么是过滤器链

过滤器链的执行顺序是怎样的

过滤器链的概念

浏览器端请求Web资源,如果经过了多个过滤器,这多个过滤器就组成了一个过滤器链。

链条中每个过滤器处理方式是一样的:如果下一个是过滤器就会把请求放行给过滤器,如果下一个是Web资源,当前就是最后一个过滤器,请求就交给Web资源。

组成过滤器链前提是:这些过滤器的过滤地址都能过滤同一个Web资源。

过滤器链有一个接口:FilterChain

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9LvtSEi-1599131780597)(assets/1552919196802.png)]

FilterChain接口中的方法

void doFilter(ServletRequest request, ServletResponse response) 参数1:请求对象 参数2:响应对象 将请求和响应向后传递,导致调用链中的下一个过滤器,或者如果调用过滤器是链中的最后一个过滤器,则导致调用链末端的Web资源。

示例:过滤器链 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qOw3LQ4Z-1599131780655)(assets/1552919236666.png)]

需求

创建两个过滤器OneFilter和TwoFilter,访问ResourceServlet,每个过滤器的请求和响应各输出一句话,观察过滤器的执行过程。

执行效果:

 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J6xQu2Ct-1599131780658)(assets/1552919267302.png)]

第一个过滤器

package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; import java.sql.Time; @WebFilter(filterName = "OneFilter", urlPatterns = "/*") public class OneFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println(new Time(System.currentTimeMillis()) + " 执行过滤器1的请求"); chain.doFilter(req, resp); //过滤器链的方法,放行 System.out.println(new Time(System.currentTimeMillis()) + " 执行过滤器1的响应"); } public void init(FilterConfig config) throws ServletException { } }

第二个过滤器

package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; import java.sql.Time; @WebFilter(filterName = "TwoFilter", urlPatterns = "/*") public class TwoFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println(new Time(System.currentTimeMillis()) + " 执行过滤器2的请求"); chain.doFilter(req, resp); System.out.println(new Time(System.currentTimeMillis()) + " 执行过滤器2的响应"); } public void init(FilterConfig config) throws ServletException { } }

Web资源

package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.sql.Time; /** * 这是web资源 */ @WebServlet("/resource") public class ResourceServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println(new Time(System.currentTimeMillis()) + " 访问Web资源"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }

疑问:过滤器链的执行顺序是怎样的?

注:要么使用注解,要么使用配置的方式

使用web.xml的配置方式: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--如果有多个过滤器,哪个过滤器配置在前面,哪个就先执行 --> <filter> <filter-name>one</filter-name> <filter-class>com.itheima.filter.OneFilter</filter-class> </filter> <filter-mapping> <filter-name>one</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>two</filter-name> <filter-class>com.itheima.filter.TwoFilter</filter-class> </filter> <filter-mapping> <filter-name>two</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> 使用注解的配置方式: 按过滤器类名的字母顺序排序,哪个在前面,哪个就先执行

小结

过滤器链的执行顺序是?

配置:按出现在web.xml中先后顺序注解:按过滤器类名的字母顺序排序,哪个在前面,哪个就先执行

11. 案例:过滤敏感词汇

需求

当用户发帖的时候,如果发出敏感词汇就进行过滤,并提示发贴失败,否则显示正常的发贴内容。

案例效果

在表单中输入含有非法字符的言论,点击提交按钮

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aMHgMS3F-1599131780663)(assets/Snipaste_2020-03-27_11-00-41.png)]

控制台显示如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lW9anHB7-1599131780666)(assets/Snipaste_2020-03-27_11-00-32.png)]

正常发贴的情况

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-94Ar7qTZ-1599131780675)(assets/Snipaste_2020-03-27_11-00-58.png)]

案例分析

创建一个表单用于发表言论。创建一个PostWordServlet,正常接收用户的输入信息,并且打印到浏览器创建一个words.txt文件,其中存入非法字符。创建一个Filter,只过滤PostWordServlet。 在init方法中将txt文件中的非法字符读取到List集合中。注:指定字符的编码为utf-8在doFilter方法中,获取请求中的参数,遍历上面的List集合,判断请求的文字中是否包含非法字符。如果言论中含有非法字符,就拦截,并且直接在过滤器中打印提示:非法言论,退出过滤器。否则就放行

实现步骤

创建一个表单,用于发表言论 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>发贴</title> </head> <body> <form method="post" action="PostWordServlet"> 发贴:<br> <textarea name="message" rows="5" cols="40"></textarea></br> <hr/> <input type="submit" value="提交"> </form> </body> </html> 创建一个txt文件,存入非法字符。要注意,文件存储使用的UTF-8字符集,否则可能出现乱码。 建议直接使用提供的word.txt文件,放在src目录下。 穷逼 笨蛋 白痴 王八 贱人 傻逼 创建一个servlet用于接受表单提交的内容 package com.itheima.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/PostWordServlet") public class PostWordServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); out.print("<h2>你发贴的内容如下:</h2>"); //获取文本域的内容 String message = request.getParameter("message"); out.print(message); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } } 创建一个过滤器,用来拦截请求,过滤请求中发表的言论的非法字符 package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.ArrayList; import java.util.List; /** * 要过滤Servlet */ @WebFilter(filterName = "WordFilter", urlPatterns = "/PostWordServlet") public class WordFilter implements Filter { //保存文件中非法字符 private List<String> words = new ArrayList<>(); //在初始化的方法中读取文本文件的内容,放在一个集合中 public void init(FilterConfig config) throws ServletException { //1.获取文件words.txt的输入流,类加载器默认从根目录下读取资源,类对象默认是从当前类所在的包中读取资源 InputStream in = this.getClass().getResourceAsStream("/words.txt"); //2.使用转换流把字节流转成字符流 try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, "utf-8"))) { String line = null; //3. 如果不为空,就继续向下读取一行 while ((line = reader.readLine()) != null) { //每一行要添加到集合中 words.add(line); } } catch (IOException e) { e.printStackTrace(); } System.out.println("读取到的集合:" + words); } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; //解决汉字乱码的问题 request.setCharacterEncoding("utf-8"); //获取用户提交的内容 String message = request.getParameter("message"); //遍历集合 for (String word : words) { //判断文字中是否包含非法字符,如果包含就拦截 if (message.contains(word)) { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); out.print("您的言论非法,发贴无效"); //退出方法,没有放行,不会进入到Servlet中去 return; } } //不包含就放行 chain.doFilter(req, resp); } public void destroy() { } }

小结

过滤敏感词汇过滤器的开发步骤:

读取src下文本文件,放在一个集合中在过滤的方法中去判断用户的输入是否包含指定的字符如果包含就拦截,没有包含就放行

12. 扩展:使用装饰者模式进行改进

目标

使用装饰者模式改进上面的功能,只替换敏感词汇为*号,其它内容不变

回顾装饰者模式

什么是装饰者模式

装饰者模式是在不改变原类文件,使用继承的情况下,动态地扩展一个类的功能。

它是通过创建一个子类对象,也就是装饰对象来包裹真实的对象。

使用场景

在开发过程中,如果发现某个类的某个(某些)方法不满足需求(不够用),那么可以使用装饰者模式对该类 进行装饰,增强这个类的方法。

装饰者模式的作用:专门对类的方法进行增强!

装饰者模式中的各个角色

抽象角色(HttpServletRequest接口):给出一个抽象接口或父类,以规范准备接收附加功能的对象(即具体角色)。具体角色(HttpServletRequestWrapper实现类):定义一个将要接收附加功能的类。装饰角色(继承于HttpServletRequestWrapper):持有一个具体角色的实例,继承于抽象角色或具体角色,给具体角色添加附加功能的类。

步骤

继承于被装饰的类(要进行功能增强类HttpServletRequestWrapper)需要在构造方法中传入原来的对象重写需要增强的方法:getParameter()重写的方法中调用原来的方法,对结果进行增强 调用原来的方法,包含有脏话的内容对脏话的集合进行遍历如果信息中包含了某个元素使用replaceAll进行替换返回替换好的字符串 过滤器中需要放行增强后的请求对象

代码

package com.itheima.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.ArrayList; import java.util.List; /** * 要过滤Servlet */ @WebFilter(filterName = "WordFilter", urlPatterns = "/PostWordServlet") public class WordFilter implements Filter { //保存文件中非法字符 private List<String> words = new ArrayList<>(); //在初始化的方法中读取文本文件的内容,放在一个集合中 public void init(FilterConfig config) throws ServletException { //1.获取文件words.txt的输入流,类加载器默认从根目录下读取资源,类对象默认是从当前类所在的包中读取资源 InputStream in = this.getClass().getResourceAsStream("/words.txt"); //2.使用转换流把字节流转成字符流 try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, "utf-8"))) { String line = null; //3. 如果不为空,就继续向下读取一行 while ((line = reader.readLine()) != null) { //每一行要添加到集合中 words.add(line); } } catch (IOException e) { e.printStackTrace(); } System.out.println("读取到的集合:" + words); } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; //解决汉字乱码的问题 request.setCharacterEncoding("utf-8"); //创建一个增强以后的请求对象 WordRequestWrapper requestWrapper = new WordRequestWrapper(request); //放行的是增强以后的请求对象 chain.doFilter(requestWrapper, resp); } public void destroy() { } /** * 写成一个内部类,可以直接使用List集合 * HttpServletRequestWrapper类没有无参的构造方法 */ class WordRequestWrapper extends HttpServletRequestWrapper { //请求对象,没有增强前的对象 private HttpServletRequest request; //构造方法,接收外面传递进来的请求对象,这个请求对象就是我们要增强的对象 public WordRequestWrapper(HttpServletRequest request) { super(request); this.request = request; } //重写父类的这个方法,对这个方法进行增强 @Override public String getParameter(String name) { //调用原来的方法,获取用户发贴的内容 String message = request.getParameter(name); //遍历非法内容的集合 for (String word : words) { //判断内容是否包含脏话 if (message.contains(word)) { //将文本中的脏话换成*号 message = message.replaceAll(word, "**"); } } //到这里message就是已经替换完成了 return message; } } }

小结

装饰者模式:对现在的类方法的功能进行增强,不用修改原有的类

13. 监听器的概述

目标

监听器的作用

常用的监听器有哪些

作用

监听作用域的创建和销毁监听作用域中属性的变化

回顾:三种作用域的创建与销毁时机

作用域接口名作用范围生命周期请求域HttpServletRequest一个用户的一次请求一次请求结束会话域HttpSession一个用户的所有请求会话过期上下文域ServletContext所有用户的所有请求服务器关闭的时候

监听器接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cUElCnqy-1599131780678)(assets/1552919771117.png)]

小结

上下文域创建和销毁的监听接口:ServletContextListener上下文域属性修改的监听接口:ServletContextAttributeListener

14. ServletContextListener监听器【重点】

目标

ServletContextListener接口有哪些方法

编写ServletContextListener监听器

ServletContextListener监听器的概述

作用:监听上下文域的创建和销毁创建时机: 服务器启动并且加载当前项目的时候销毁时机: 服务器关闭的时候

案例:ServletContextListener的应用

需求:

在Web项目加载和结束的时候在控制台各打印输出现在的时间戳,并且输出一句话

代码

监听器
package com.itheima.listener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebListener; import java.sql.Timestamp; /** * 监听器类似于JS中事件 * 创建监听器的步骤 * 1. 创建一个类实现ServletContextListener接口 * 2. 重写接口中方法,包含两个方法,分别对应创建和销毁 * 3. 在web.xml中配置监听器或使用注解@WebListener */ //@WebListener public class MyContextListener implements ServletContextListener { //监听上下文对象的创建 @Override public void contextInitialized(ServletContextEvent event) { System.out.println(event.getServletContext() + "创建了上下文对象"); System.out.println(new Timestamp(System.currentTimeMillis()) + "上下文对象创建"); } //监听上下文对象的销毁 @Override public void contextDestroyed(ServletContextEvent event) { System.out.println(new Timestamp(System.currentTimeMillis()) + "上下文对象销毁"); } }
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--配置监听器--> <listener> <listener-class>com.itheima.listener.MyContextListener</listener-class> </listener> </web-app>

执行效果

org.apache.catalina.core.ApplicationContextFacade@d7685c6创建了上下文对象 2020-09-03 16:10:09.16上下文对象创建 2020-09-03 16:10:25.959上下文对象销毁

小结

ServletContextListener接口中的方法

接口中的方法功能执行次数void contextDestroyed(ServletContextEvent sce)监听上下文对象的销毁1次void contextInitialized(ServletContextEvent sce)监听上下文对象的创建1次

ServletContextEvent事件对象的方法

ServletContextEvent中的方法功能ServletContext getServletContext()获取创建的上下文对象

15. ServletContextAttributeListener监听器

目标

ServletContextAttributeListener监听器触发的时机

ServletContextAttributeListener接口方法中的方法

ServletContextAttributeListener监听器的作用

作用:监听上下文域中属性的增删改操作

时机:

增加属性:setAttribute()删除属性:removeAttribute()修改属性:setAttribute(同名)

ServletContextAttributeListener监听器的示例

案例需求

创建一个ServletContextAttributeListener监听器的实现类,重写接口中所有的方法,输出属性名和属性值

创建一个Servlet,向context上下文中添加一个属性,修改一个属性,删除一个属性。

注:修改后的属性值,要通过上下文对象来取得。

案例效果

向上下文域中添加了属性名:user,值:孙悟空 修改了上下文域中属性名:user,修改前的值:孙悟空,修改后的值:白骨精 删除了上下文域中属性名:user,值:白骨精

案例代码

package com.itheima.listener; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebListener; import java.sql.Timestamp; /** * 监听器类似于JS中事件 * 创建监听器的步骤 * 1. 创建一个类实现ServletContextListener接口 * 2. 重写接口中方法,包含两个方法,分别对应创建和销毁 * 3. 在web.xml中配置监听器或使用注解@WebListener */ //@WebListener public class MyContextListener implements ServletContextListener, ServletContextAttributeListener { //监听上下文对象的创建 @Override public void contextInitialized(ServletContextEvent event) { System.out.println(event.getServletContext() + "创建了上下文对象"); System.out.println(new Timestamp(System.currentTimeMillis()) + "上下文对象创建"); } //监听上下文对象的销毁 @Override public void contextDestroyed(ServletContextEvent event) { System.out.println(new Timestamp(System.currentTimeMillis()) + "上下文对象销毁"); } //监听上下文域属性添加 @Override public void attributeAdded(ServletContextAttributeEvent event) { //getName()获取属性名, getValue()获取属性值 System.out.println("向上下文域中添加了属性名:" + event.getName() + ",值:" + event.getValue()); } //监听上下文域属性删除 @Override public void attributeRemoved(ServletContextAttributeEvent event) { System.out.println("删除了上下文域中属性名:" + event.getName() + ",值:" + event.getValue()); } //监听上下文域属性修改 @Override public void attributeReplaced(ServletContextAttributeEvent event) { //获取上下文对象 ServletContext application = event.getServletContext(); //通过名字获取值,获取修改后的 Object value = application.getAttribute(event.getName()); System.out.println("修改了上下文域中属性名:" + event.getName() + ",修改前的值:" + event.getValue() + ",修改后的值:" + value); } }

小结

ServletContextAttributeListener接口中的方法

接口中的方法功能void attributeAdded(ServletContextAttributeEvent event)监听上下文域的添加事件void attributeRemoved(ServletContextAttributeEvent event)监听上下文域的删除事件void attributeReplaced(ServletContextAttributeEvent event)监听上下文域的修改事件

ServletContextAttributeEvent对象中的方法

ServletContextAttributeEvent对象中的方法功能String getName()获取属性名Object getValue()获取属性值

16. 案例:统计网站当前在线人数

执行效果

页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ILwkvo3-1599131780680)(assets/image-20200903165954858.png)]

服务器控制台信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2EJypeDJ-1599131780682)(assets/Snipaste_2020-03-27_16-30-54.png)]

分析

每当一个用户访问项目的时候,都会创建一个session会话。所以当session会话被创建,当前在线用户+1,每当session会话被销毁,当前在线用户-1。

HttpSessionListener可以用来监听session对象的创建和销毁的。所以可以在HttpSessionListener中的监听session对象创建和销毁的方法中控制在线人数的加减。

步骤

创建一个监听器 SessionCountListener

创建一个成员变量AtomicInteger,用于计数。注:必须是同一个对象

a) 监听会话创建的方法

​ i. 从上下文域中取出当前的计数对象

​ ii. 如果为空,表示是第1个用户,设置值为1,并且添加到上下文域中

​ iii. 不为空则加1,不用更新上下文域

​ b) 监听会话销毁的方法

​ i. 从上下文域中得到当前在线的人数

​ ii. 减1即可

创建一个注销的LogoutServlet

a) 让会话失效

b) 打印输出:您已经安全退出网站

编写JSP

a) 在JSP上取出上下文域中用户数显示

b) 显示安全退出的链接

代码

监听器

package com.itheima.listener; import javax.servlet.ServletContext; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import java.util.concurrent.atomic.AtomicInteger; /** * 创建一个监听器,监听会话的创建和销毁 */ @WebListener public class SessionCountListener implements HttpSessionListener { //创建一个成员变量计数,考虑线程安全问题 private AtomicInteger number; //监听会话的创建 @Override public void sessionCreated(HttpSessionEvent event) { //获取会话对象 HttpSession session = event.getSession(); //通过会话对象获取上下文对象 ServletContext application = session.getServletContext(); //从上下文域中获取number的值 number = (AtomicInteger) application.getAttribute("number"); //第1个用户访问的时候number是为空 if (number == null) { //创建一个1的值 number = new AtomicInteger(1); application.setAttribute("number", number); } else { //不为空,加1 number.incrementAndGet(); //因为AtomicInteger这是一个引用类型,获取的是它的地址,不需要进行更新 } //在控制台输出 System.out.println("创建会话:" + session.getId() + ",当前在线人数:" + number); } //监听会话的销毁 @Override public void sessionDestroyed(HttpSessionEvent event) { //获取会话对象 HttpSession session = event.getSession(); //通过会话对象获取上下文对象 ServletContext application = session.getServletContext(); //从上下文域中获取number的值 number = (AtomicInteger) application.getAttribute("number"); //减1 number.decrementAndGet(); //在控制台输出 System.out.println("销毁会话:" + session.getId() + ",当前在线人数:" + number); } }

退出

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>退出</title> </head> <body> 退出成功 <% session.invalidate(); %> </body> </html>

显示人数的JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>显示在线人数</title> </head> <body> <%--所有的用户都可以访问,这个计数的值放在上下文域中--%> <h3>当前在线人数是:${applicationScope.number}</h3> <a href="logout.jsp">退出</a> </body> </html>

小结

HttpSessionListener接口的作用是什么?

监听会话的创建和销毁

说说以下方法的作用:

HttpSessionListener接口中的方法作用void sessionCreated(HttpSessionEvent event)监听会话的创建void sessionDestroyed(HttpSessionEvent event)监听会话的销毁

学习总结

能够说出过滤器的作用

修改请求拦截请求

应用场景:

全局乱码问题用户权限拦截过滤敏感词汇

能够编写过滤器

创建一个类实现javax.servlet.Filter接口重写所有的方法,其中doFilter方法是执行过滤的方法使用web.xml配置或@WebFilter("/过滤地址")注解进行配置

能够说出过滤器生命周期相关方法

Filter接口中的方法作用和执行次数void init(FilterConfig filterConfig)初始化的时候执行1次,服务器启动的时候void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)每次请求都会执行public void destroy()服务器关闭的时候执行,执行1次

能够根据过滤路径判断指定的过滤器是否起作用

匹配方式匹配哪些资源以/开头精确匹配:/demo1目录匹配: /目录/*匹配所有的资源:/*以扩展名结尾*.扩展名 注:/开头和扩展名结尾不能同时出现

能够说出什么是过滤器链

执行顺序:

xml配置方式:哪个配置在前面就先执行哪个注解的方式:按过滤器类名的字母先后顺序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZARWRwTu-1599131780683)(assets/1552919236666.png)]

能够编写过滤器解决全局乱码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RlMeEY3T-1599131780685)(assets/1552918911663.png)]

能够说出监听器的作用

监听作用域的创建和销毁监听作用域的属性变化

能够使用ServletContextListener监听器

创建一个类实现ServletContextListener接口重写监听创建和销毁的方法在web.xml中配置或使用@WebListener注解
最新回复(0)