一、filter过滤器的认识
1.1 filter过滤器的概念及用法
(1)filter的概念
- 过滤器——Filter,它是JavaWeb三大组件之一。另外两个是Servlet和Listener。它是在2000年发布的Servlet2.3规范中加入的一个接口。是Servlet规范中非常实用的技术。它可以对web应用中的所有资源进行拦截,并且在拦截之后进行一些特殊的操作。常见应用场景:URL级别的权限控制;过滤敏感词汇;中文乱码问题等等。
(2)过滤器入门案例
- 创建javaweb工程
编写和配置接收请求用的servlet
/*** 用于接收和处理请求的Servlet* @author 黑马程序员* @Company http://www.itheima.com*/public class ServletDemo1 extends HttpServlet {/*** 处理请求的方法* @param req* @param resp* @throws ServletException* @throws IOException*/@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("ServletDemo1接收到了请求");//请求转发req.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(req,resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doGet(req,resp);}}
在web.xml文件中配置servlet ```xml <?xml version=”1.0” encoding=”UTF-8”?>
<servlet-name>ServletDemo1</servlet-name><servlet-class>com.itheima.web.servlet.ServletDemo1</servlet-class>
<servlet-name>ServletDemo1</servlet-name><url-pattern>/ServletDemo1</url-pattern>
- 编写index.jsp文件```xml<%-- Created by IntelliJ IDEA. --%><%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head><title>主页面</title></head><body><a href="${pageContext.request.contextPath}/ServletDemo1">访问ServletDemo1</a></body></html>
- 编写success.jsp文件 ```xml <%@ page contentType=”text/html;charset=UTF-8” language=”java” %> <%System.out.println(“success.jsp执行了”);%> 执行成功!
- 准备编写过滤器```java/*** Filter的入门案例* @author 黑马程序员* @Company http://www.itheima.com*/public class FilterDemo1 implements Filter {/*** 过滤器的核心方法* @param request* @param response* @param chain* @throws IOException* @throws ServletException*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {/*** 如果不写此段代码,控制台会输出两次:FilterDemo1拦截到了请求。*/HttpServletRequest req = (HttpServletRequest) request;String requestURI = req.getRequestURI();if (requestURI.contains("favicon.ico")) {return;}System.out.println("FilterDemo1拦截到了请求");}}
在web.xml文件中配置filter过滤器
<!--配置过滤器--><filter><filter-name>FilterDemo1</filter-name><filter-class>com.itheima.web.filter.FilterDemo1</filter-class></filter><filter-mapping><filter-name>FilterDemo1</filter-name><!--/*:表示拦截所有的请求路径--><url-pattern>/*</url-pattern></filter-mapping>
部署和测试
(3)改进过滤器的编写
- 将
标签中的 的拦截路径配置成专门要拦截的路径

- 然后使用FilterChain中的doFilter方法执行放行操作。

(4)过滤器的使用细节
- 过滤器的api介绍
- Filter


- FilterConfig

- FilterChain

(5)Filter的生命周期
- 出生:当应用加载时执行实例化和初始化方法。
- 活着:只要应用一直提供服务,对象就一直存在;
- 死亡:当应用卸载或服务器宕机时,对象消亡。
- Filter的实例对象在内存中也只有一份,所以也是单例的。
Filter过滤器核心的方法细节
/*** 过滤器的核心方法* @param request* @param response* @param chain* @throws IOException* @throws ServletException*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {/*** 如果不写此段代码,控制台会输出两次:FilterDemo1拦截到了请求。HttpServletRequest req = (HttpServletRequest) request;String requestURI = req.getRequestURI();if (requestURI.contains("favicon.ico")) {return;}*/System.out.println("FilterDemo1拦截到了请求");//过滤器放行chain.doFilter(request,response);System.out.println("FilterDemo1放行之后,又回到了doFilter方法");}
因此过滤器在执行时,放行前会拦截到请求,放行后还会回到过滤器中。
(6)过滤器初始化参数配置
创建过滤器
/*** Filter的初始化参数配置* @author 黑马程序员* @Company http://www.itheima.com*/public class FilterDemo2 implements Filter {private FilterConfig filterConfig;/*** 初始化方法* @param filterConfig* @throws ServletException*/@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("FilterDemo2的初始化方法执行了");//给过滤器配置对象赋值this.filterConfig = filterConfig;}/*** 过滤器的核心方法* @param request* @param response* @param chain* @throws IOException* @throws ServletException*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("FilterDemo2拦截到了请求");//过滤器放行chain.doFilter(request,response);}/*** 销毁方法*/@Overridepublic void destroy() {System.out.println("FilterDemo2的销毁方法执行了");}}
配置过滤器
<filter><filter-name>FilterDemo2</filter-name><filter-class>com.itheima.web.filter.FilterDemo2</filter-class><!--配置过滤器的初始化参数--><init-param><param-name>filterInitParamName</param-name><param-value>filterInitParamValue</param-value></init-param></filter><filter-mapping><filter-name>FilterDemo2</filter-name><url-pattern>/ServletDemo1</url-pattern></filter-mapping>
在FilterDemo2的doFilter方法中添加下面的代码 ```java //根据名称获取过滤器的初始化参数 String paramValue = filterConfig.getInitParameter(“filterInitParamName”); System.out.println(paramValue);
//获取过滤器初始化参数名称的枚举
Enumeration
//获取ServletContext对象 ServletContext servletContext = filterConfig.getServletContext(); System.out.println(servletContext);
//获取过滤器名称 String filterName = filterConfig.getFilterName(); System.out.println(filterName);
- 多个servlet配置在web.xml文件中的执行顺序时从上到下执行的,程序代码运行也是根据web.xml文件配置的顺序执行的。而决定顺序的标签是<filter-mapping>。<a name="KeXkN"></a>### (7)过滤器的四种拦截行为- 我们的过滤器目前拦截的是请求,但是在实际开发中,我们还有请求转发和请求包含,以及由服务器触发调用的全局错误页面。默认情况下过滤器是不参与过滤的,要想使用,需要我们配置。配置的方式如下:```xml<!--配置过滤器--><filter><filter-name>FilterDemo1</filter-name><filter-class>com.itheima.web.filter.FilterDemo1</filter-class><!--配置开启异步支持,当dispatcher配置ASYNC时,需要配置此行--><async-supported>true</async-supported></filter><filter-mapping><filter-name>FilterDemo1</filter-name><url-pattern>/ServletDemo1</url-pattern><!--过滤请求:默认值。--><dispatcher>REQUEST</dispatcher><!--过滤全局错误页面:当由服务器调用全局错误页面时,过滤器工作--><dispatcher>ERROR</dispatcher><!--过滤请求转发:当请求转发时,过滤器工作。--><dispatcher>FORWARD</dispatcher><!--过滤请求包含:当请求包含时,过滤器工作。它只能过滤动态包含,jsp的include指令是静态包含--><dispatcher>INCLUDE</dispatcher><!--过滤异步类型,它要求我们在filter标签中配置开启异步支持--><dispatcher>ASYNC</dispatcher></filter-mapping>
(8)过滤器与Servlet的区别
| 方法/类型 | Servlet | Filter | 备注说明 |
|---|---|---|---|
| 初始化方法 | void init(ServletConfig config) | void init(FilterConfig config) | 几乎一样,都是在web.xml中配置参数,用该对象的方法可以获取到。 |
| 提供服务的方法 | void service(request,response) | void doFilter(request,response,FilterChain) | Filter比Servlet多了一个FilterChain,它不仅能完成Servlet的功能,而且还可以决定程序是否能继续执行。所以过滤器比Servlet更为强大。 在Struts2中,核心控制器就是一个过滤器 |
| 销毁方法 | void destroy(); | void destroy(); |
1.2 过滤器的使用案例
(1)静态资源设置缓存时间过滤器
- 需求说明
- 在我们访问html,js,image时,不需要每次都重新发送请求读取资源,就可以通过设置响应消息头的方式,设置缓存时间。但是如果每个Servlet都编写相同的代码,显然不符合我们统一调用和维护的理念。(此处有个非常重要的编程思想:AOP思想,在录制视频时提不提都可以)因此,我们要采用过滤器来实现功能。
编写步骤
- 第一步:创建javaWeb工程
- 第二步:导入静态资源
第三步:编写过滤器 ```java /**
- 静态资源设置缓存时间
- html设置为1小时
- js设置为2小时
- css设置为3小时
- @author 黑马程序员
- @Company http://www.itheima.com */ public class StaticResourceNeedCacheFilter implements Filter {
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; }
public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {//1.把doFilter的请求和响应对象转换成跟http协议有关的对象HttpServletRequest request;HttpServletResponse response;try {request = (HttpServletRequest) req;response = (HttpServletResponse) res;} catch (ClassCastException e) {throw new ServletException("non-HTTP request or response");}//2.获取请求资源URIString uri = request.getRequestURI();//3.得到请求资源到底是什么类型String extend = uri.substring(uri.lastIndexOf(".")+1);//我们只需要判断它是不是html,css,js。其他的不管//4.判断到底是什么类型的资源long time = 60*60*1000;if("html".equals(extend)){//html 缓存1小时(根据名称获取过滤器初始化参数的值)String html = filterConfig.getInitParameter("html");time = time*Long.parseLong(html);}else if("js".equals(extend)){//js 缓存2小时(根据名称获取过滤器初始化参数的值)String js = filterConfig.getInitParameter("js");time = time*Long.parseLong(js);}else if("css".equals(extend)){//css 缓存3小时(根据名称获取过滤器初始化参数的值)String css = filterConfig.getInitParameter("css");time = time*Long.parseLong(css);}//5.设置响应消息头response.setDateHeader("Expires", System.currentTimeMillis()+time);//6.放行chain.doFilter(request, response);}public void destroy() {}
}
- 第四步:配置过滤器```xml<filter><filter-name>StaticResourceNeedCacheFilter</filter-name><filter-class>com.itheima.web.filter.StaticResourceNeedCacheFilter</filter-class><init-param><param-name>html</param-name><param-value>3</param-value></init-param><init-param><param-name>js</param-name><param-value>4</param-value></init-param><init-param><param-name>css</param-name><param-value>5</param-value></init-param></filter><filter-mapping><filter-name>StaticResourceNeedCacheFilter</filter-name><url-pattern>*.html</url-pattern></filter-mapping><filter-mapping><filter-name>StaticResourceNeedCacheFilter</filter-name><url-pattern>*.js</url-pattern></filter-mapping><filter-mapping><filter-name>StaticResourceNeedCacheFilter</filter-name><url-pattern>*.css</url-pattern></filter-mapping>
- 第五步:测试结果(注意:chrome浏览器刷新时,每次也都会发送请求,所以看不到304状态码。建议用IE浏览器,因为它在刷新时不会再次请求。)
(2)特殊字符过滤器
- 需求说明
- 在实际开发中,可能会面临一个问题,就是很多输入框都会遇到特殊字符。此时,我们也可以通过过滤器来解决。例如:我们模拟一个论坛,有人发帖问:“在HTML中表示水平线的标签是哪个?”。如果我们在文本框中直接输入
就会出现一条水平线,这个会让发帖人一脸懵。我们接下来就用过滤器来解决一下。
- 在实际开发中,可能会面临一个问题,就是很多输入框都会遇到特殊字符。此时,我们也可以通过过滤器来解决。例如:我们模拟一个论坛,有人发帖问:“在HTML中表示水平线的标签是哪个?”。如果我们在文本框中直接输入
编写步骤
- 第一步:创建javaWeb工程
第二步:编写servlet和JSP ```java /**
- @author 黑马程序员
- @Company http://www.itheima.com */ public class ServletDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取请求参数 String content = request.getParameter(“content”); response.getWriter().write(content); }
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response); }
}
```xml<servlet><servlet-name>ServletDemo1</servlet-name><servlet-class>com.itheima.web.servlet.ServletDemo1</servlet-class></servlet><servlet-mapping><servlet-name>ServletDemo1</servlet-name><url-pattern>/ServletDemo1</url-pattern></servlet-mapping>
<!--JPS页面--><%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title></title></head><body><!--请求 表单--><form action="${pageContext.request.contextPath}/ServletDemo1" method="POST">回帖:<textarea rows="5" cols="25" name="content"></textarea><br/><input type="submit" value="发言"></form></body></html>
- 第三步:编写过滤器 ```java
/**
- @author 黑马程序员
@Company http://www.itheima.com */ public class HTMLFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {HttpServletRequest request;HttpServletResponse response;try {request = (HttpServletRequest) req;response = (HttpServletResponse) res;} catch (ClassCastException e) {throw new ServletException("non-HTTP request or response");}//创建一个自己的Request类MyHttpServletRequest2 myrequest = new MyHttpServletRequest2(request);//放行:chain.doFilter(myrequest, response);}public void destroy() {}
} class MyHttpServletRequest2 extends HttpServletRequestWrapper { //提供一个构造方法 public MyHttpServletRequest2(HttpServletRequest request){ super(request); }
//重写getParameter方法public String getParameter(String name) {//1.获取出请求正文: 调用父类的获取方法String value = super.getParameter(name);//2.判断value是否有值if(value == null){return null;}return htmlfilter(value);}//自定义的HTML过滤方法private String htmlfilter(String message){if (message == null)return (null);char content[] = new char[message.length()];message.getChars(0, message.length(), content, 0);StringBuilder result = new StringBuilder(content.length + 50);for (int i = 0; i < content.length; i++) {switch (content[i]) {case '<':result.append("<");break;case '>':result.append(">");break;case '&':result.append("&");break;case '"':result.append(""");break;default:result.append(content[i]);}}return (result.toString());}
}
- 第四步:配置过滤器```xml<filter><filter-name>HTMLFilter</filter-name><filter-class>com.itheima.web.filter.HTMLFilter</filter-class></filter><filter-mapping><filter-name>HTMLFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
- 测试结果
二、Listener监听器的认识
2.1 Listener监听器的概念和及用法
(1)观察者模式
- 它是事件驱动的一种体现形式。就好比在做什么事情的时候被人盯着。当对应做到某件事时,触发事件。
- 观察者模式通常由以下三部分组成:
- 事件源:触发事件的对象。
- 事件:触发的动作,里面封装了事件源。
- 监听器:当事件源触发事件时,要做的事情。一般是一个接口,由使用者来实现。(此处的思想还涉及了一个涉及模式——策略模式)
- 下图描述了观察者设计模式组成:

(2)Servlet规范中的8个监听器的简介
- 监听对象创建的监听器
- ServletContextListener
```java
/**
- 用于监听ServletContext对象创建和销毁的监听器
- @since v 2.3 */
- ServletContextListener
```java
/**
public interface ServletContextListener extends EventListener {
/*** 对象创建时执行此方法。该方法的参数是ServletContextEvent事件对象,事件是【创建对象】这个动作* 事件对象中封装着触发事件的来源,即事件源,就是ServletContext*/public default void contextInitialized(ServletContextEvent sce) {}/*** 对象销毁执行此方法*/public default void contextDestroyed(ServletContextEvent sce) {}
}
- HttpSessionListener```java/*** 用于监听HttpSession对象创建和销毁的监听器* @since v 2.3*/public interface HttpSessionListener extends EventListener {/*** 对象创建时执行此方法。*/public default void sessionCreated(HttpSessionEvent se) {}/*** 对象销毁执行此方法*/public default void sessionDestroyed(HttpSessionEvent se) {}}
ServletRequestListener
/*** 用于监听ServletRequest对象创建和销毁的监听器* @since Servlet 2.4*/public interface ServletRequestListener extends EventListener {/*** 对象创建时执行此方法。*/public default void requestInitialized (ServletRequestEvent sre) {}/*** 对象销毁执行此方法*/public default void requestDestroyed (ServletRequestEvent sre) {}}
- 监听域中属性发生变化的监听器
- ServletContextAttributeListener
```java
/**
- 用于监听ServletContext域(应用域)中属性发生变化的监听器
- @since v 2.3 */
- ServletContextAttributeListener
```java
/**
public interface ServletContextAttributeListener extends EventListener { /**
* 域中添加了属性触发此方法。参数是ServletContextAttributeEvent事件对象,事件是【添加属性】。* 事件对象中封装着事件源,即ServletContext。* 当ServletContext执行setAttribute方法时,此方法可以知道,并执行。*/public default void attributeAdded(ServletContextAttributeEvent scae) {}/*** 域中删除了属性触发此方法*/public default void attributeRemoved(ServletContextAttributeEvent scae) {}/*** 域中属性发生改变触发此方法*/public default void attributeReplaced(ServletContextAttributeEvent scae) {}
}
- HttpSessionAttributeListener```java/*** 用于监听HttpSession域(会话域)中属性发生变化的监听器* @since v 2.3*/public interface HttpSessionAttributeListener extends EventListener {/*** 域中添加了属性触发此方法。*/public default void attributeAdded(HttpSessionBindingEvent se) {}/*** 域中删除了属性触发此方法*/public default void attributeRemoved(HttpSessionBindingEvent se) {}/*** 域中属性发生改变触发此方法*/public default void attributeReplaced(HttpSessionBindingEvent se) {}}
ServletRequestAttributeListener
/*** 用于监听ServletRequest域(请求域)中属性发生变化的监听器* @since Servlet 2.4*/public interface ServletRequestAttributeListener extends EventListener {/*** 域中添加了属性触发此方法。*/public default void attributeAdded(ServletRequestAttributeEvent srae) {}/*** 域中删除了属性触发此方法*/public default void attributeRemoved(ServletRequestAttributeEvent srae) {}/*** 域中属性发生改变触发此方法*/public default void attributeReplaced(ServletRequestAttributeEvent srae) {}}
和会话域相关的两个感知型监听器
- 和会话域相关的两个感知型监听器是无需配置的,直接编写代码即可。
HttpSessionBinderListener ```java /**
- 用于感知对象和和会话域绑定的监听器
- 当有数据加入会话域或从会话域中移除,此监听器的两个方法会执行。
- 加入会话域即和会话域绑定
- 从会话域移除即从会话域解绑 */ public interface HttpSessionBindingListener extends EventListener {
/**
- 当数据加入会话域时,也就是绑定,此方法执行 */ public default void valueBound(HttpSessionBindingEvent event) { }
/**
- 当从会话域移除时,也就是解绑,此方法执行 */ public default void valueUnbound(HttpSessionBindingEvent event) { } }
- HttpSessionActivationListener```java/*** 用于感知会话域中对象钝化和活化的监听器*/public interface HttpSessionActivationListener extends EventListener {/*** 当会话域中的数据钝化时,此方法执行*/public default void sessionWillPassivate(HttpSessionEvent se) {}/*** 当会话域中的数据活化时(激活),此方法执行*/public default void sessionDidActivate(HttpSessionEvent se) {}}
(3)监听器的使用
- 在实际开发中,我们可以根据具体情况来从这8个监听器中选择使用。感知型监听器由于无需配置,只需要根据实际需求编写代码,所以此处我们就不再演示了。我们在剩余6个中分别选择一个监听对象创建销毁和对象域中属性发生变化的监听器演示一下。
ServletContextListener的使用
- 第一步创建工程
第二步编写监听器
/*** 用于监听ServletContext对象创建和销毁的监听器* @author 黑马程序员* @Company http://www.itheima.com*/public class ServletContextListenerDemo implements ServletContextListener {/*** 对象创建时,执行此方法* @param sce*/@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("监听到了对象的创建");//1.获取事件源对象ServletContext servletContext = sce.getServletContext();System.out.println(servletContext);}/*** 对象销毁时,执行此方法* @param sce*/@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println("监听到了对象的销毁");}}
第三步在web.xml文件中配置监听器
<!--配置监听器--><listener><listener-class>com.itheima.web.listener.ServletContextListenerDemo</listener-class></listener>
测试结果
ServletContextAttributeListener的使用
- 第一步创建工程
第二步编写监听器 ```java /**
- 监听域中属性发生变化的监听器
- @author 黑马程序员
- @Company http://www.itheima.com */ public class ServletContextAttributeListenerDemo implements ServletContextAttributeListener {
/**
- 域中添加了数据
@param scae / @Override public void attributeAdded(ServletContextAttributeEvent scae) { System.out.println(“监听到域中加入了属性”); /*
- 由于除了我们往域中添加了数据外,应用在加载时还会自动往域中添加一些属性。
我们可以获取域中所有名称的枚举,从而看到域中都有哪些属性 */
//1.获取事件源对象ServletContext ServletContext servletContext = scae.getServletContext(); //2.获取域中所有名称的枚举 Enumeration
names = servletContext.getAttributeNames(); //3.遍历名称的枚举 while(names.hasMoreElements()){ //4.获取每个名称 String name = names.nextElement(); //5.获取值 Object value = servletContext.getAttribute(name); //6.输出名称和值 System.out.println(“name is “+name+” and value is “+value); } }
/**
- 域中移除了数据
- @param scae */ @Override public void attributeRemoved(ServletContextAttributeEvent scae) { System.out.println(“监听到域中移除了属性”); }
/**
- 域中属性发生了替换
- @param scae */ @Override public void attributeReplaced(ServletContextAttributeEvent scae) { System.out.println(“监听到域中属性发生了替换”); } }
/ 同时,我们还需要借助第一个ServletContextListenerDemo监听器,往域中存入数据,替换域中的 数据以及从域中移除数据,代码如下: / /**
- 用于监听ServletContext对象创建和销毁的监听器
- @author 黑马程序员
@Company http://www.itheima.com */ public class ServletContextListenerDemo implements ServletContextListener {
/**
- 对象创建时,执行此方法
@param sce */ @Override public void contextInitialized(ServletContextEvent sce) { System.out.println(“监听到了对象的创建”); //1.获取事件源对象 ServletContext servletContext = sce.getServletContext(); //2.往域中加入属性 servletContext.setAttribute(“servletContext”,”test”); }
/**
- 对象销毁时,执行此方法
- @param sce */ @Override public void contextDestroyed(ServletContextEvent sce) { //1.取出事件源对象 ServletContext servletContext = sce.getServletContext(); //2.往域中加入属性,但是名称仍采用servletContext,此时就是替换 servletContext.setAttribute(“servletContext”,”demo”); System.out.println(“监听到了对象的销毁”); //3.移除属性 servletContext.removeAttribute(“servletContext”); } } ```
- 第三步在web.xml文件中配置监听器
```xml
com.itheima.web.listener.ServletContextListenerDemo
- 第四步测试结果
2.2 监听器的综合案例
(1)需求说明
- 解决乱码问题
- 学生管理系统中,肯定会有请求和响应的中文乱码问题。而乱码问题在学习Servlet的课程中已经讲解了如何解决了。只是在实际开发中,当有很多的Servlet时,肯定不能在每个Servlet中都编写一遍解决乱码的代码。因此,就可以利用我们今天学习的过滤器来实现统一解决请求和响应乱码的问题。
- 检查登录
