Java Web之使用过滤器
write:2022-4-22
前文我们学习了JSP页面引用Java Bean:Java Web之JSP使用Java Bean,这节课我们学习Java Web的过滤器。
在实际应用中,每个web组件都需要检查客户请求的代码,如果每个web组件都编写请求的代码,显然重复编码降低开发效率和可维护性,为了解决这个问题,过滤器产生,过滤器是Servlet规范2.3才产生的技术。
过滤器能够对一部分客户请求进行预处理操作,然后再把请求转发给实际上对应的web组件,对于生成的响应结果,过滤器还能进行检查和修改,再把修改后的结果响应给客户。
文章目录
1. 过滤器的概念
过滤器是在Java Servlet规范2.3中定义的,它能够对客户请求和web组件生成的响应对象进行检查和修改(预处理)。
过滤器本身并不生成请求和响应对象,它只提供过滤作用。
过滤器能够在Web组件被调用之前检查Request对象,修改Request Header和Request内容;
在Web组件被调用之后检查Response对象,修改Response Header和Response内容。
过滤器可以为Servlet、JSP或HTML文件等Web组件提供过滤。
2. 过滤器的工作过程
3. 创建过滤器的方法
3.1 Filter接口
所有的过滤器类都必须实现javax.servlet.Filter接口。这个接口含有3个过滤器类必须实现的方法,三个方法分别在过滤器不同生命周期被调用:
init()
doFilter()
destroy()
(1)init(FilterConfig):这是过滤器的初始化方法,Servlet容器创建过滤器实例后将调用这个方法。在这个方法中可以读取web.xml 文件中过滤器的初始化参数
(2)doFilter(ServletRequest, ServletResponse,FilterChain):
这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL时,Servlet容器将先调用过滤器的doFilter方法。FilterChain参数用于访问后续过滤器或者web组件;上面方法的三个参数是由Servlet容器传的
(3)destroy():Servlet容器在销毁过滤器实例前调用该方法,在这个方法中可以释放过滤器占用的资源
3.2 过滤器的例子
下面创建一个NoteFilter过滤器,它可以拒绝列在黑名单上的客户访问留言簿,而且能将Web组件响应客户请求所花的时间写入日志。
3.2.1 NoteFilter.java
假定客户真正请求的web组件是NoteServlet,而NoteServlet前面有一个NoteFilter过滤器:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class NoteFilter implements Filter {
private FilterConfig config = null;
private String blackList=null;
public void init(FilterConfig config) throws ServletException {
this.config = config;
blackList=config.getInitParameter("blacklist");
}
public void destroy() {
config = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String username =((HttpServletRequest) request).getParameter("username");
if (username!=null )username=new String(username.getBytes("ISO-8859-1"),"GB2312");
if (username!=null && username.indexOf(blackList) != -1 ) {
response.setContentType("text/html;charset=GB2312");
PrintWriter out = response.getWriter();
out.println("<html><head></head><body>");
out.println("<h1>对不起,"+username + ",你没有权限留言 </h1>");
out.println("</body></html>");
out.flush();
return;
}
long before = System.currentTimeMillis();
config.getServletContext().log("NoteFilter:before call chain.doFilter()");
chain.doFilter(request, response);
config.getServletContext().log("NoteFilter:after call chain.doFilter()");
long after = System.currentTimeMillis();
String name = "";
if (request instanceof HttpServletRequest) {
name = ((HttpServletRequest)request).getRequestURI();
}
config.getServletContext().log("NoteFilter:"+name + ": " + (after - before) + "ms");
}
}
代码流程分析:
当NoteFilter初始化时,调用init()方法,有一个FilterConfig参数,它包含了配置信息,而这些配置信息一开始存放在web.xml文件中,它调用config.getInitParameter(“blacklist”)方法,从web.xml文件中读取初始化参数blacklist,这个参数表示被禁止访问留言簿的客户黑名单。
在NoteFilter的doFilter()方法中首先从request对象中读取客户姓名,然后将客户姓名转换为中文字符编码。如果客户姓名中包含黑名单里的字符串,那么将直接向客户端返回一个拒绝网页。
由于在这种情况下没有调用chain.doFilter()方法,因此客户请求不会到达客户请求真正所访问的Web组件。
所以根据条件就有过滤器拒绝请求和转发请求两种情况:
3.2.2 NoteFilter过滤器拒绝HTTP请求
假定姓名中包含“捣蛋鬼”字符串的客户将被禁止访问留言簿,并且留言簿由NoteServlet类来实现,当名叫“捣蛋鬼2000”的客户访问留言簿时,将被NoteFilter过滤器拒绝访问。返回结果:
当名叫“捣蛋鬼2000”的客户访问留言簿时NoteFilter的工作流程:
“捣蛋鬼2000”的客户请求的web组件是NoteServlet,但有NoteFilter过滤器在,根本没有到NoteServlet,返回结果的拒绝网页也是有NoteFilter生成的;
3.2.3 NoteFilter过滤器转发HTTP请求
当不在黑名单中的客户请求访问,调用doFilter()方法,在此方法中,又会调用chain.doFilter()方法:
long before = System.currentTimeMillis();//调用chain.doFilter()方法前的时间
config.getServletContext().log(
"NoteFilter:before call chain.doFilter()");
chain.doFilter(request, response);//如果当前过滤器与后续过滤器有关联,就转发给后续过滤器,如果没有就把请求转发给后续的web组件
config.getServletContext().log(
"NoteFilter:after call chain.doFilter()");
long after = System.currentTimeMillis();//调用chain.doFilter()方法后的时间
String name = "";
if (request instanceof HttpServletRequest) {
name = ((HttpServletRequest)request).getRequestURI();
}
config.getServletContext().log("NoteFilter:"+name + ": " + (after - before) + "ms");//相减计算出NoteServlet响应客户请求所花的时间
当名叫“小精灵”的客户访问留言簿时,将被NoteFilter过滤器转发请求访问。返回结果:
计算调用chain.doFilter()方法之前和之后的时间:
当名叫“小精灵”的客户访问留言簿时NoteFilter的工作流程:
4. 发布过滤器的方法
4.1 配置过滤器
(1)发布过滤器时,必须在web.xml文件中加入< filter>元素和< filter-mapping>元素。< filter>元素用来定义过滤器,< filter-mapping>元素用于将过滤器和URL映射:
<filter-mapping>
<filter-name>NoteFilter</filter-name>
<url-pattern>/note</url-pattern>
</filter-mapping>
在上面的例子中:
<filter>
<filter-name>NoteFilter</filter-name>
<filter-class>mypack.NoteFilter</filter-class>
<init-param> //设置参数
<param-name>blacklist</param-name>
<param-value>捣蛋鬼</param-value>
</init-param>
</filter>
通过config.getInitParameter(“blacklist”)就可以得到参数值;
(2)< filter-mapping>元素用于将过滤器和URL映射,如果希望过滤器能为所有的URL过滤,那么可以把< url-pattern>的值设为“/* ”。这样,当客户请求访问Web应用中的任何一个Web组件时,Servlet容器都会先把请求交给过滤器处理;
(3)在web.xml文件中,必须先配置过滤器,再配置Servlet;
4.2 创建NoteServlet类
创建一个Servlet类NoteServlet,它实现一个简单的留言簿。它提供了一个表单,让客户输入姓名和留言,客户提交表单后,再将用户输入的信息显示在客户端的网页上。
NoteServlet.java:
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class NoteServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html;charset=GB2312";
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>留言薄</title></head>");
out.println("<body>");
String username=request.getParameter("username");
String content=request.getParameter("content");
if(username!=null)username=new String(username.getBytes("ISO-8859-1"),"GB2312");
if(content!=null)content=new String(content.getBytes("ISO-8859-1"),"GB2312");
if(content!=null && !content.equals(""))
out.println("<p>"+username+"的留言为:"+content+"</P>");
out.println(" <FORM action="+request.getContextPath()+"/note method=POST>");
out.println("<b>姓名:</b>");
out.println("<input type=text size=10 name=username ><br>");
out.println("<b>留言:</b><br>");
out.println("<textarea name=content rows=5 cols=20 wrap></textarea><br>");
out.println("<BR>");
out.println("<input type=submit value=提交>");
out.println("</form>");
out.println("</body></html>");
}
public void destroy() {
}
}
NoteServlet的配置代码:
<servlet>
<servlet-name>NoteServlet</servlet-name>
<servlet-class>mypack.NoteServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>NoteServlet</servlet-name>
<url-pattern>/note</url-pattern>//映射路径与NoteFilter一致
</servlet-mapping>
访问NoteServlet:
http://localhost:8080/应用名/note(先处理NoteFilter再处理NoteServlet)
5. 多个过滤器协同工作
(1)串联过滤器的工作流程
过滤器1和过滤器2都为Servlet工作,先调用过滤器1再调用过滤器2
(2)配置串联的过滤器
因为是先调用过滤器1再调用过滤器2,所以先配置过滤器1再配置过滤器2
<filter>
<filter-name>Filter1</filter-name>
<filter-class>Filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter1</filter-name>
<url-pattern>/note</url-pattern>
</filter-mapping>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>Filter2</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter2</filter-name>
<url-pattern>/note</url-pattern>
</filter-mapping>
6. 练习题
- 问题:在过滤器中能否访问application范围内的共享数据?
答案:可以的,先调用FilterConfig的getServletContext()方法获得ServletContext,再调用ServletContext的getAttribute()方法来获得application范围内的共享数据。 - 问题:“过滤器只能对Servlet进行过滤”,这句话是否正确?
答案:不正确。过滤器可以对Servlet、JSP和HTML文件过滤。