JavaWeb三大组件之过滤器(Filter)

过滤器可以动态的拦截请求和响应,以变换或使用包含在请求或响应中的信息。

过滤器是可用于Servlet编程的Java类,可以实现以下目的:

  • 在客户端的请求访问后端资源之前,拦截这些请求。
  • 在服务器的响应发送回客户端之前,处理这些响应。

过滤器通过 Web 部署描述符(web.xml)中的 XML 标签来声明,然后映射到你的应用程序的部署描述符中的 Servlet 名称或 URL 模式。

当 Web 容器启动 Web 应用程序时,它会为你在部署描述符中声明的每一个过滤器创建一个实例。

Filter的执行顺序与在web.xml配置文件中的配置顺序一致,一般把Filter配置在所有的Servlet之前。

1.编写过滤器

如何编写过滤器?

  1. 创建一个类,必须实现Filter接口
  2. 在web.xml中进行配置,一般把Filter配置在所有的Servlet配置之前

方法介绍:

  • void init():Filter的初始化,Filter在服务器启动时就创建,创建之后马上执行这个方法。用来初始化一些参数
  • void doFilter(req,resp,chain):当向服务器请求的Servlet或jsp页面在过滤器的过滤范围内时就会执行这个方法。若方法体中没有chain.doFilter()操作,则表示当向服务器请求该过滤器过滤范围内的资源(如Servlet/JSP页面/html页面等)时,这些资源中的所有方法都不会执行(被过滤掉了);若方法体中有chain.doFilter()操作,表示不对过滤器过滤范围内的资源进行过滤。(即你请求的资源下的方法会执行)
  • void destroy():在服务器关闭时对Filter进行销毁,在Filter销毁之前会执行这个方法,用来对非内存资源进行释放。

对方法中设计到的类介绍:

  • FilterConfig:与ServletConfig相似,该类有如下四个方法:
    • getInitParameter():获取初始化参数。
    • getInitParameterNames():获取所有初始化参数的名称。
    • getFilterName():获取过滤器的配置名称。
    • getServletContext():获取application。
  • FilterChain类:该类中有一个方法:
    • doFilter():是不是会觉得该方法与Filter接口中的doFilter()方法是一样的呢?没错,二者虽然外观看起来一样,但功能却是千差万别的。该方法被FilterChain对象调用,表示对Filter过滤器过滤范围下的资源进行放行。

2.多过滤器的执行顺序

Web应用程序可以根据特定的目的定义若干个不同的过滤器,那么就需要在web.xml中对多个过滤器进行多个配置。而在web.xml中使用<filter-mapping>来控制多个过滤器的执行顺序,即哪个过滤器的<filter-mapping>配置在web.xml中的顺序排在前面那这个过滤器就先执行。

3.过滤器的四种拦截方式

  • 1.拦截直接请求方式:REQUEST
  • 2.拦截请求转发方式:FORWARD
  • 3.拦截请求包含方式:INCLUDE
  • 4.拦截错误转发方式:ERROR

实现不同的拦截方式需要在中进行不同的配置:

  • <dispatcher>REQUEST</dispatcher>
  • <dispatcher>FORWORD</dispatcher>
  • <dispatcher>INCLUDE</dispatcher>
  • <dispatcher>ERROR</dispatcher>

若在web.xml配置文件中没有写出上面四个拦截配置时默认该过滤器只拦截请求。

4.过滤器的应用场景

1.执行目标资源之前做”预处理”工作,例如设置编码,这种通常都会放行,只是在目标资源执行之前做一些准备工作。(例如:几乎是所有的Servlet中都需要写request.setCharacteEncoding(),可以把它放入到一个Filter中。)这种过滤器没有拦截功能。

2.通过条件判断是否放行,例如校验当前用户是否已经登录,或者用户IP是否已经被禁用。(有拦截操作) (粗粒度权限控制,会员有会员的权利、游客有游客的权利)

3.在目标资源执行后,做一些后续的特殊处理工作。例如把目标资源输出的数据进行处理。

5.案例1:分IP统计网站的访问次数

功能分析:1.统计工作需要在所有资源之前都执行,那么就可以放到Filter中了。2.我们这个过滤器不打算做拦截操作,因为我们只是用来做统计的。3.用什么东西来装载统计的数据。Map,整个网站只需要一个Map即可4.Map什么时候创建(使用ServletContextListener,在服务器启动时完成创建,并保存到SevletContext中),Map保存到哪里:Map需要在Filter中用来保存数据;Map需要在页面使用,打印Map中的数据。

AListener.java:

AFilter.java:

show.jsp:

效果图:

6.案例2:解决全站字符乱码问题

一般我们通过jsp页面请求转发到servlet时,若请求方式为POST且请求参数包含中文参数时,我们需要在servlet的doPost()方法中设置POST请求编码问题:request.setCharacterEncoding("utf-8");、设置响应编码问题:response.setContentType("text/html;charset=utf-8");,这样便可以解决post请求即响应编码问题;而对于GET请求,若传递的请求参数包含中文参数时设置请求编码就比较麻烦,需要在servlet的doGet()方法中设置响应编码:response.setContentType("text/html;charset=utf-8");以及请求编码:首先获得传递给servlet的请求参数:String username=request.getParameter("username")假设传递的请求参数为username,然后再输入代码username=new String(username.getBytes("ISO8859-1"),"utf-8");,这样通过jsp页面转发到servlet的参数便解决了编码问题。即可以通过response.getWrite().prinltn(username)正常显示在网页上。

试想:以后的开发中往往会用到很多的servlet,那我们岂不是要在每一个servlet的doPost()和doGet方法中都写上上述的解决编码代码?这时候我们就可以通过过滤器来解决了。

首先附上页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="<c:url value="/AServlet?username=张三"/> ">点击这里</a>

<form action="<c:url value="/AServlet"/> " method="post">
用户名:<input type="text" name="username" value="李四">
<input type="submit" value="提交">
</form>
</body>
</html>

显示在网页上的界面为:

通过”点击这里”的链接我们便完成了通过jsp页面向servlet发送GET请求参数,通过”提交”按钮我们便完成了通过jsp页面向servlet发送POST请求参数。创建一个servlet,我们在servlet中完成响应参数编码的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AServlet extends HttpServlet {



protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

response.setContentType("text/html;charset=utf-8");

}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

response.setContentType("text/html;charset=utf-8");

}
}

接下来在过滤器中完成请求参数编码的问题,创建一个过滤器Filter,在web.xml中注册:

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>Filter</filter-name>
<filter-class>filter.Filter</filter-class>
</filter>

<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Filter中编码为:

1
2
3
4
5
6
7
8
9
10
11
12
public class Filter implements javax.servlet.Filter {
public void destroy() {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {

}

public void init(FilterConfig config) throws ServletException {

}
}

对于POST请求参数的编码设置我们直接在doFilter()方法体中添加request.setCharacterEncoding("utf-8");代码即可(此时运行程序,POST请求参数编码的问题成功解决),对于GET请求参数的编码,有些同学会觉得直接在doFilter()方法体中添加

1
String  username=request.getParameter("username");username=new String(username.getBytes("ISO-8859-1"),"utf-8");

即可。这样的参数是不太靠谱的,因为这里我们知道要传递的请求参数为username所以这里可以明了的指出,以后我们不知道请求参数为什么或者请求参数有很多时那就需要更多的上诉代码,所以这里我们采用装饰者模式对request进行装饰(即将本来的request换成我们自己写的request),创建一个EncodingRequest.java继承HttpServletRequestWrapper,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class EncodingRequest extends HttpServletRequestWrapper
{

private HttpServletRequest req;

public EncodingRequest(HttpServletRequest request)
{

super(request);
this.req=request;
}

@Override
public String getParameter(String name) {
String value=req.getParameter(name);


//处理编码问题
try {
value=new String(value.getBytes("ISO-8859-1"),"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return value;
}
}

在构造方法中,我们传入系统的request,然后将这个request赋值给我们自己编写的req,然后在重写的getParameter()方法中通过我们自己写的req获取请求参数并解决编码问题,然后返回解决完编码后的参数value(此时这个中文参数已解决编码),然后在Filer中对我们自己编写的request(即Encodingquest对象)放行即可。现在doFilter()方法的方法体为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
//处理post请求编码问题
request.setCharacterEncoding("utf-8");

HttpServletRequest req= (HttpServletRequest) request;
/**
* 处理get请求的编码问题
*/

// String username=request.getParameter("username");
// username=new String(username.getBytes("ISO-8859-1"),"utf-8");
/**
* 调包request
* 1.写一个request的装饰类
* 2.在放行时,使用我们自己的request
*/


EncodingRequest er = new EncodingRequest(req);
chain.doFilter(er, response);
}

运行程序,成功解决GET请求方式的编码问题,但是POST请求方式的编码又出现了问题,这是为什么呢?因为我们在doFilter方法中已经通过代码request.setCharacterEncoding("utf-8");处理了POST请求方式的编码问题,但是此时的请求是系统的request对象而不是我们自己写的req,我们对req进行了放行而没有对request进行方式,所以方法体中应该增加if判断语句,改正后的doFilter()方法体内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {

//处理post请求编码问题
request.setCharacterEncoding("utf-8");

HttpServletRequest req= (HttpServletRequest) request;
/**
* 处理get请求的编码问题
*/

// String username=request.getParameter("username");
// username=new String(username.getBytes("ISO-8859-1"),"utf-8");

/**
* 调包request
* 1.写一个request的装饰类
* 2.在放行时,使用我们自己的request
*/

if (req.getMethod().equals("GET")) {
EncodingRequest er = new EncodingRequest(req);

chain.doFilter(er, response);
}else if (req.getMethod().equals("POST")){
chain.doFilter(request, response);

}
}

此时运行程序,成功解决POST请求方式和GET请求方式的编码问题。在学习框架之前我们都这样通过Filter解决编码问题,而当我们学习了Spring MVC框架后我们处理POST请求参数的编码问题时直接在web.xml中添加如下配置而不用再写一个过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

解决GET请求方式的编码问题时有两种解决方法:1.修改tomcat配置文件添加编码与工程编码一致,如下:

1
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>

2.对参数进行重新编码:

1
2
String userName new 
String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")

第二种方法需要对每个参数都进行重新编码,比较麻烦。

回归我们的过滤器讲解,通过如上包装request的方式便可以通过过滤器解决全站编码问题。

源码请点击这里前往我的github

2018.3.19更

欢迎加入我的Java交流1群:659957958。

2018.4.21更:如果群1已满或者无法加入,请加Java学习交流2群:305335626

7.联系

If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.

记得扫一扫领一下红包再走哦