JavaWeb——过滤器和监听器
目录
1. 过滤器
在 JSP 的 Web 应用程序中,过滤器是一种在服务端运行的 Web 组件程序,它可以截取客户端给服务器发的请求,也可以截取服务器给客户端的响应,如图 1 所示。
那么过滤器在什么地方使用呢?下面列举了 5 个过滤器使用的场景。 1:在网上的一些评论中经常看到不文明的词汇会被*所代替,这种功能的实现就是在用户提交评论时,评论内容先先经过过滤器,过滤器将不文明词汇替换成*,然后将过滤后的评论内容传递到目标文件。 2:表单提交的中文需要进行请求编码设置,可以通过过滤器统一进行请求编码设置。 3:可以通过过滤器为上传的图片统一添加水印。 4:对客户端请求进行权限认证,可以通过滤器统一进行处理。 5:可以对响应数据进行统一处理。
当 Web 容器获得一个对资源的请求时,Web 容器判断是否存在过滤器和这个资源关联。如果存在关联就把请求交给过滤器去处理,在过滤器中可以对请求的内容做出改变,然后再将请求转交给被请求的资源。当被请求的资源做出响应时,Web 容器同样会将响应先转发给过滤器,在过滤器中可以对响应做出处理然后再将响应发送给客户端。在这整个过程中客户端和目标资源是不知道过滤器的存在。
在一个 Web 应用程序中可以配置多个过滤器,从而形成过滤器链。在请求资源时,过滤器链中的过滤器依次对请求作出处理。在接受到响应时再按照相反的顺序对响应作出处理,如图 2 所示。
需要注意的是在过滤器中不一定必须将请求发送给被请求资源,也可以直接给客户端做出响应。
开发过滤器需要以下两个步骤
- 定义过滤器类,实现 javax.servlet.Filter 接口
- 重写 init()方法、doFilter()方法、destroy()方法
- 配置过滤器
1.1 开发过滤器
第一步:创建过滤器类
在 eclipse 中创建一个新的 Dynamic Web Project,命名为 myFilter。在 myFilter 项目中创建包 cn.itlaobing.filter,在该包中创建类 FirstFilter 类,代码如下
package cn.itlaobing.filter; import java.io.IOException; import javax.servlet.*; public class FirstFilter implements javax.servlet.Filter { public void init(FilterConfig config) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { System.out.println("过滤器 1 请求"); chain.doFilter(request, response); System.out.println("过滤器 1 响应"); } public void destroy() { } }
代码解析
- 过滤器是实现 javax.servlet.Filter 接口的类,并重写 init()、doFilter()、destroy()方法。
- Init()方法完成过滤器的初始化工作,由 Web 容器来调用。
- destroy()方法用来释放资源,由 Web 容器来调用。
- doFilter()方法类似于 Servlet 中的 service()方法,当客户端请求于此过滤器关联的目标对象时,就会调用过滤器的 doFilter()方法。在这个方法中利用 FilterChain 接口中的 doFilter()方法将请求交个下个过滤器来处理,如果该过滤器是过滤器链中的最后一个过滤器,则将请求交给被请求资源,也可以直接给客户端返回响应信息。
第二步:配置过滤器
过滤器类定义后,还需要在 web.xml 中进行过滤器配置,通过配置设置用户访问哪些资源时需要经过该过滤器。
web.xml 配置
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <filter> <filter-name>firstFilter</filter-name> <filter-class>cn.itlaobing.filter.FirstFilter</filter-class> </filter> <filter-mapping> <filter-name>firstFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
代码解析
- 节点描述该 Filter 对应的类。
- 中的必须和节点中的值相同
- 配置过滤器类的存储路径
- 指定该过滤器关联的 URL,比如/*指的是对所以资源都过滤,/admin/*指的是对 admin 目录下的所有资源进行过滤。
第三步:测试过滤器
在 myFilter 项目中添加一个 jsp 文件,命名为 test.jsp。然后将 myFilter 项目部署到
tomcat 容器中,启动 tomcat 服务器。访问 test.jsp 文件,观察 eclipse 控制台输出的内容如下
1.2 开发过滤器链
在之前的基础上再添加一个过滤器,实现过滤器链。
第一步:定义过滤器类
在 myFilter 项目的包 cn.itlaobing.filter 中添加一个类,命名为 SecondFilter,代码如下
package cn.itlaobing.filter; import java.io.IOException; import javax.servlet.*; public class SecondFilter implements javax.servlet.Filter { public void init(FilterConfig config) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { System.out.println("过滤器 2 请求");// 后续过滤器执行前,先执行该行代码 chain.doFilter(request, response);// 将请求与响应对象向后续过滤器传递 System.out.println("过滤器 2 响应");// 后续过滤器执行后,再执行该行代码 } public void destroy() { } }
第二步:配置过滤器
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <filter> <filter-name>firstFilter</filter-name> <filter-class>cn.itlaobing.filter.FirstFilter</filter-class> </filter> <filter-mapping> <filter-name>firstFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>secondFilter</filter-name> <filter-class>cn.itlaobing.filter.SecondFilter</filter-class> </filter> <filter-mapping> <filter-name> secondFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
第三步:测试过滤器
在浏览器的地址栏中请求 test.jsp,控制台输出结果如图 3 所示:
通过图 3 的结果可以得知在调用 FilterChain 对象的 doFilter 方法之前的代码都是对请求的过滤,在此之后的都是对响应的过滤。整个过程如下:
那么怎么来确定一个过滤器在过滤器链中的顺序呢?这个是根据过滤器在 web.xml 中配置的上下顺序来决定的,配置在前面的过滤器先执行,配置在后面的过滤器后执行。
如果要正确的获得表单中的中文或给客户端输出中文那
么就要在 Servlet 中添加如下两行代码:
request.setCharacterEncoding(“UTF-8”);
response.setCharacterEncoding(“UTF-8”);
如果在每个 Servlet 中都添加这样的两行代码,无疑是代码的重复。我们可以将这两行代码放在过滤器中进行处理。
1.3 使用过滤器实现汉字编码过滤器
第一步:定义过滤器类
在 myFilter 项目的包 cn.itlaobing.filter 中添加一个类,命名为 CharacterFilter,代码如下
package cn.itlaobing.filter; import java.io.IOException; import javax.servlet.*; public class CharacterFilter implements Filter { private String encode = "utf-8"; @Override // 从 web.xml 配置文件中读取默认编码,如果没有配置编码,默认使用 utf-8 编码 public void init(FilterConfig filterConfig) throws ServletException { // FilterConfig 用于从 web.xml 中配置读取默认编码 encode = filterConfig.getInitParameter("encode"); if (encode == null) {encode = "UTF-8"; } else {encode = encode.toUpperCase(); } } @Override public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {HttpServletRequest request =(HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if ("POST".equalsIgnoreCase(request.getMethod())) {request.setCharacterEncoding(encode);request.setCharacterEncoding(encode);response.setContentType("text/html;charset=" + encode);chain.doFilter(request, response);return; } chain.doFilter(request, response); } @Override public void destroy() { } }
代码解析
- Init()方法从 web.xml 配置文件中读取编码方式,若配置文件中没有设置编码方式,默认使用 utf-8 编码。
- 在 doFilter()方法中判断请求方式若为 POST 方式,则对请求编码和响应编码进行了设置。
第二步:配置过滤器
<!-- 配置汉字编码过滤器 --> <filter> <filter-name>CharacterFilter</filter-name> <filter-class>cn.itlaobing.filter.CharacterFilter</filter-class> </filter> <filter-mapping> <filter-name>CharacterFilter</filter-name> <url-pattern>/*
第三步:测试过滤器
下面通过表单提交中文来测试过滤器是否对中文提交进行了编码。在 myFilter 项目中添加一个 jsp 文件,命名为 add.jsp,代码如下
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>测试编码过滤器</title> </head> <body> <form action="MyServlet" method="post"> 请输入汉字<input type="text" name="key" value="我是汉字"> <input type="submit" value="发送"> </form> </body> </html>
在 myFilter 项目中添加一个获取表单数据的 Servlet,命名为 MyServlet,代码如下
package cn.itlaobing.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; @WebServlet("/MyServlet") public class MyServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String key = request.getParameter("key"); System.out.println("key="+key); } }
部署项目,在浏览器中打开 add.jsp 文件,点击添加按钮,观察 eclipse 控制台输出的结果,发现汉字显示正常,表明过滤器工作正常。
2. 监听器
监听器的作用是监听 Web 应用程序中某一个对象,并根据应用程序的需求做出相应的处理,Java Web 应用程序中,Servlet 容器提供了多种监听器,详见表 1:
监听器 | 描述 |
---|---|
ServletRequestListener | 监听 request 对象的创建和销毁 |
ServletRequestAttributeListener | 监听向 request 作用域赋值和取值 |
HttpSessionListener | 监听 session 对象的创建和销毁 |
HttpSessionAttributeListener | 监听向 session 作用域赋值和取值 |
ServletContextListener | 监听 application 对象的创建和销毁 |
ServletContextAttributeListener | 监听向 application 作用域赋值和取值 |
开发过滤器需要以下三个步骤
- 定义监听器类,实现监听器接口
- 重写相应的方法
- 配置监听器
2.1 统计在线人数
下面使用监听器实现在线人数统计功能。思路如下,当有用户访问时,web 容器会创建会话;当有用户退出时,web 容器会销毁会话。HttpSessionListener 监听器能够监听会话的创建和销毁,在会话创建时,在线人数加 1,在会话销毁时,在线人数减 1。在线人数需要存储在变量中,这个变量的值需要能够在每一位访问者的页面上显示,因此该变量存储在 application 作用域中。
在 IDEA 项目中创建一个 Dynamic Web Project,命名为 online,在 online 项目中添加包 cn.itlaobing.listener。
第一步:创建 application 对象监听器
在 cn.itlaobing.listener 包中创建类 ServletContextListenerImpl,实现 javax. servlet. ServletContextListener 接口,用于监听 application 对象的创建和销毁。当 application 创建时(web 容器启动时),设置在线人数为 0,当 application 对象销毁时(web 容器关闭时),清空在线人数。代码如下
package cn.itlaobing.listener; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class ServletContextListenerImpl implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { int num = 0; ServletContext application = event.getServletContext(); application.setAttribute("onLineNum", num); } public void contextDestroyed(ServletContextEvent event) { ServletContext application = event.getServletContext(); application.removeAttribute("onLineNum"); } }
代码解析
- ServletContextListener 监听器用于监听 application 对象的创建和销毁
- 当 application 创建时,监听器自动调用 contextInitialized()方法
- 当 application 对象销毁时,监听器自动调用 contextDestroyed()方法
- contextInitialized()方法中的参数 ServletContextEvent 的 getServletContext()方法用于获取被创建的 application 对象,并将在线人数 0 保存到键为 onLineNum 的application 作用域中。
- 当 application 对象销毁时,在 contextDestroyed()方法中将键为 onLineNum 的对象从application 作用域中移除。
第二步:配置 application 监听器
在 web.xml 中配置 application 监听器,代码如下
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <listener> <listener-class>cn.itlaobing.listener.ServletContextListenerImpl</listener-class> </listener> </web-app>
代码解析
- 节点配置监听器
- 指明监听器类的路径
第三步:创建 session 监听器
在 cn.itlaobing.listener 包中创建类 HttpSessionListenerImpl,代码如下
package cn.itlaobing.listener; import javax.servlet.ServletContext; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class HttpSessionListenerImpl implements HttpSessionListener { public void sessionCreated(HttpSessionEvent event) { ServletContext application = event.getSession().getServletContext(); Integer num = (Integer) application.getAttribute("onLineNum"); if (num != null) {int count = num;count = count + 1;application.setAttribute("onLineNum", count); } else {application.setAttribute("onLineNum", 1); } } public void sessionDestroyed(HttpSessionEvent event) { ServletContext application = event.getSession().getServletContext(); Integer num = (Integer) application.getAttribute("onLineNum"); int count = num; count = count - 1; application.setAttribute("onLineNum", count); } }
代码解析
- 当 Session 被创建时,会调用监听器的 sessionCreated 方法。
- 当 Session 被销毁时,会调用监听器的 sessionDestroyed 方法。
- 在 sessionCreated()方法中实现在线人数加 1 的功能。
- 在 sessionDestroyed()方法中实现在线人数减 1 的功能。
第四步:配置 session 监听器
在 web.xml 中配置 session 监听器,代码如下
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <listener> <listener-class>cn.itlaobing.listener.ServletContextListenerImpl</listener-class> </listener> <listener> <listener-class>cn.itlaobing.listener.HttpSessionListenerImpl</listener-class> </listener> </web-app>
第五步:在界面上显示在线人数
在 online 项目中,添加一个 jsp 文件,命名为 online.jsp,online.jsp 用于显示在线人数,代码如下
<body>当前在线人数:${onLineNum } </body>
第六步:测试在线人数
将项目部署到 tomcat 容器,启动 tomcat 容器,在浏览器中访问该项目的 online.jsp 文件,查看在线人数。