过滤器(Filter)
什么是Filter?
Servlet API中提供了一个Filter接口,开发Web应用时,如果编写的Java类实现了这个接口,则把这个类称之为过滤器Filter。
通过Filter技术,开发人员可以实现用户在访问某个目标资源前后进行拦截处理。简单的说,就是可以实现web容器对某个资源的访问前截获并进行相关的处理,也可以在某资源向web容器放回响应前进行截获并进行相关的处理。
常用的Filter:
验证过滤器
日志记录和审计过滤器
图像转换过滤器
数据压缩过滤器
加密过滤器
标记过滤器
触发资源访问事件的过滤器
MIME类型链过滤器
XSL/T过滤器
Filter常用方法:
void init(FilterConfig filterConfig):Filter被初始化时调用(只会被调用一次)。
void destory():此方法仅在过滤器的所在线程都退出之后调用,或在过了超时期之后调用(只会被调用一次)。
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)(注意这里面的参数类型,是ServletRequest和ServletResponse而非HttpServletRequest和HttpServletResponse):当要访问过滤器的过滤的资源前后都会通过此方法(此方法能在多个线程中被调用)。在这个过程中对请求的内容或相应的数据进行处理。此方法的典型实现遵循以下模式:
- 检查请求
- 有选择的将带有自定义实现的请求对象装入过滤器
- 有选择的将带有自定义实现的响应对象装入过滤器
- 可以使用FilterChain对象的doFilter方法调用链中的下一个实体;也可以不将请求/响应传递给过滤器中的下一个实体,从而阻塞请求处理
- 在调用过滤器链中的下一个实体之后直接设置响应头
Filter执行原理:
FilterChain
FilterChain是Servlet容器为开放人员提供的对象。过滤器使用FilterChain调用链中的下一个过滤器,如果调用的过滤器是链中的最后一个过滤器,则调用链末尾的资源。
方法:
void doFilter(ServletRequest request, ServeltResponse response)导致链中的额下一个过滤器被调用,如果调用的过滤器是链中的最后一个过滤器,则导致调用链末尾的资源。
使用Filter
实现Filter接口,实现init、doFilter、destroy方法。
配置Filter
过滤器配置文件格式:
<filter>
<filter-name>Encoding</filter-name>
<filter-class>com.li.filter.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<!-- 和工程使用的编码一致 -->
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Encoding</filter-name>
<!--
访问/control下的所有资源都会通过此过滤器
如果url-pattern的值和Servlet的相同,则只在访问对应的Servlet时使用此过滤器
注:一般不要将静态资源包含在URL之内(除非对一些静态资源也需要做处理,例如:为图片加水印)
-->
<url-pattern>/control/*</url-pattern>
</filter-mapping>
示例代码:
过滤器生命周期演示(LifecycleFilter.java)
package com.li.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/*演示过滤器生命周期的过滤器*/
public class LifecycleFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
/*此方法会在多个线程中调用,因而线程名字不一样*/
print("doFilter");
chain.doFilter(req, resp);
print("chain doFilter end");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
print("init");
}
@Override
public void destroy() {
print("destroy");
}
void print(String info) {
System.out.println(info+" thread name:"+Thread.currentThread().getName());
}
}
主页(index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<p>
<a href="login.html">用户登录</a>
</p>
<p>
<a href="control/test">访问TestServlet(需要登录后才能访问)</a>
</p>
</body>
</html>
登录页(login.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<fieldset>
<legend>用户登录</legend>
<form action="login" method="post">
<p>
用户名:<input type="text" name="name" />
</p>
<p>
密 码:<input type="password" name="password" />
</p>
<p>
<input type="submit" value="登录" /> <input type="reset" value="取消" />
</p>
</form>
</fieldset>
</body>
</html>
处理编码过滤器(EncodingFilter.java)
在此过滤器中设置了请求对象获取数据的编码类型,不用再一个一个去设置编码
package com.li.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/*处理编码的过滤器*/
public class EncodingFilter implements Filter {
String encoding="GBK";
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
req.setCharacterEncoding(encoding);
chain.doFilter(req, resp);
}
@Override
public void init(FilterConfig config) throws ServletException {
//获取初始化参数:值为"UTF-8"
encoding=config.getInitParameter("encoding");
}
@Override
public void destroy() {
}
}
验证登录过滤器(LoginValidationFilter.java)
此过滤器用于未登录就访问服务而出现的空指针异常以及设置响应对象的内容类型的一些处理。
package com.li.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/*用于登录验证的Filter*/
public class LoginValidationFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
//强制类型转换,这样才能获取session
HttpServletRequest request=(HttpServletRequest) req;
HttpServletResponse response=(HttpServletResponse) resp;
HttpSession session =request.getSession();
if(session == null || session.getAttribute("user")==null) {
response.sendRedirect("../login.html");
}else {
/*在响应之前调用设置内容的方法*/
response.setContentType("text/html; charset=UTF-8");
chain.doFilter(request, response);
}
}
}
登录验证服务(LoginServlet.java)
package com.li.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name=req.getParameter("name");
String password=req.getParameter("password");
/*此处我们并没有配置编码,但是通过过滤器配置了编码,因此不会出现乱码*/
System.out.println(name);
if("admin".equals(name) && "123456".equals(password)) {
HttpSession session=req.getSession();
session.setAttribute("user", name);
resp.sendRedirect("index.html");
}else {
resp.sendRedirect("login.html");
}
}
}
测试服务程序(TestServlet.java)
package com.li.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session=req.getSession(false);
/*如果我们还未登录就访问此服务,则会出现空指针异常,因为病不存在一个Session可以获取到(而且并不主动创建)*/
/*我们也并未在这里设置内容类型*/
String name=(String)session.getAttribute("user");
resp.getWriter().write("<h3>欢迎"+name+"访问TestServlet</h3><a href='../index.html'>返回首页</a>");
}
}
配置文件(web.xml)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>FilterDemo</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<filter>
<filter-name>LifecycleFilter</filter-name>
<filter-class>com.li.filter.LifecycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LifecycleFilter</filter-name>
<!-- 当前工程目录下的所有资源都必须经过该过滤器 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.li.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.li.servlet.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/control/test</url-pattern>
</servlet-mapping>
<filter>
<filter-name>LoginValidationFilter</filter-name>
<filter-class>com.li.filter.LoginValidationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LoginValidationFilter</filter-name>
<!-- 将过滤器配置到和TestServlet同一个路径下,因此,该路径下所有文件都要经过此过滤器 -->
<url-pattern>/control/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.li.filter.EncodingFilter</filter-class>
<!-- 配置初始化参数 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
然而,我们通过过滤器解决请求对象获取数据时的编码问题,只是解决了一半,因为数据提交方式有两种(post,get),两种方式的处理方法各不相同。这时,我们还可以有一种简单而统一的方法去解决get提交方式在获取而出现的乱码问题。我们可以找到Eclipse中工程中名字为Servers的Tomcat配置文件夹,打开其中的server.xml,找到:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
为内部加上内容主体:useBodyEncodingForURI=”true”,即为:
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" useBodyEncodingForURI="true"/>
意思就是我们使用请求主体的编码作为URI的编码。如果我们在页面里面设置编码为UTF-8,那么我们的URI也使用UTF-8作为编码。
链里面过滤器的个数以及执行顺序
对于有效范围不同的过滤器,先执行范围大的,在执行范围小的过滤器;对于同一路径下(url-pattern)的过滤器,按照web.xml中的配置顺序依次执行。
例如,我们访问TestServlet在上面的代码中,先执行演示生命周期的过滤器,再执行处理编码的过滤器,最后执行登录验证的过滤器。