# 過濾器
[TOC]
## 導學
過濾器-Filter,其實可以見名知意,就像我們日常生活中的凈化器,過濾網一樣,用于在訪問資源文件之前,通過一系列的過濾器對請求進行修改、判斷等,把不符合規則的請求在中途攔截或修改。也可以對響應進行過濾,攔截或修改響應。
## 過濾器初識
* Filter過濾器 (Filter)是J2EE Servlet模塊下的組件
* Filter的作用是對URL進行統一的攔截處理
* Filter通常用于應用程序層面進行全局處理

**開發過濾器的三要素**
1. 任何過濾器都要實現javax.servlet.Filtere接口
2. 在filter接口的doFilter()方法中編寫過濾器的功能代碼(一個過濾器一個功能)
3. 在web.xml中對過濾器進行配置,說明攔截URL的范圍
~~~
public class MyFirstFilter implements Filter{
//銷毀
@Override
public void destroy() {
}
/**
*
* @param request
* @param response
* @param chain 過濾鏈對象
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("過濾器已生效!");
//將請求與響應對象沿著過濾鏈依次向后傳遞,如果不調用該方法,請求無法正常的向后被處理
chain.doFilter(request, response);
}
//初始化方法
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
~~~
~~~
<!-- 配置過濾器,filter標簽用于說明哪個類是過濾器,并在應用啟動的時候自動加載 -->
<filter>
<filter-name>myFirstFilter</filter-name>
<filter-class>com.dodoke.firstfilter.MyFirstFilter</filter-class>
</filter>
<!--
filter-mapping標簽用于說明過濾器對URL應用的范圍,要點有二:
1、filter-name 過濾器名稱與filter.filter-name保持一致
2、url-pattern 說明過濾器作用范圍,/*代表對所有URL進行過濾
-->
<filter-mapping>
<filter-name>myFirstFilter</filter-name>
<!-- 指明過濾器能夠過濾的請求映射范圍 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
~~~
~~~
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public HelloServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("Hello,world!!!");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
~~~
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>我是默認首頁</h1>
</body>
</html>
~~~
無論是請求servlet還是請求靜態資源的時候,只要過濾器范文允許,都會實現過濾任務。
## 過濾器的生命周期

先進行web.xml配置文件的加載,然后進行初始化工作,調用`Filter.init()`對當前過濾器進行初始化,執行時機tomcat啟動后會自動創建Filter這個過濾器對象,一旦對象創建完,馬上執行`Filter.init()`方法。提供服務Filter.doilter(),銷毀- Filter.destroy()執行時機應用關閉或者重啟時。
~~~
public class MyFirstFilter implements Filter {
@Override
public void destroy() {
System.out.println("過濾器已被銷毀");;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("過濾器已生效");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("過濾器初始化成功");
}
}
~~~
**過濾器的特性:**
* 過濾器對象在Web應用啟動時被創建且全局唯一
* 唯一的過濾器對象在并發環境中采用“多線程”提供服務(單例多線程方式)
雖然全局只有一個過濾器對象,但是如果是在并發環節中,每一個請求進來的時候,過濾器都會為其創建一個獨立的線程來提供服務,線程和線程之間他們是彼此不受影響的,正是因為叫單例多線程的這么一種設計,既保證了過濾器不會因為頻繁創建對象消耗系統資源,同時又采用多線程方式,有效提高了多用戶在訪問同一個過濾器時的執行速度
## 過濾器的兩種開發方式
在之前開發過濾器的時候,我們使用配置文件中注冊的形式完成了過濾器的開發。其實類似于servlet,過濾器的配置同樣可以使用注解完成。
~~~
過濾器注解形式
@WebFilter(filterName="",urlPatterns="")
~~~
**過濾器兩種開發方式**
* 配置形式維護性更好,適用應用全局過濾 xml中配置
* 注解形式開發體驗更好,適用于小型項目敏捷開發
~~~
//filterName 指明過濾器名稱,不同的過濾器需要保證過濾器名稱不同,否則過濾器失效。 urlPatterns指明過濾哪些URL
@WebFilter(filterName="MyAnnotationFilter",urlPatterns="/*")
public class MyAnnoationFilter implements Filter{
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("注解形式過濾器已生效");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
~~~
**Tomcat是如何發現過濾器并啟動過濾器的?**
Servlet3.0版本之后,默認對注解有了支持。每次應用程序啟動時,Tomcat會對項目內的所有class字節碼文件(java編譯后的文件)進行掃描,當掃描到WebFlter注解時,則啟用這個過濾器。
同時存在配置形式和注解形式的過濾器時,會先執行配置形式過濾器。一般來說,整個項目只需要采用一種形式的過濾器,盡可能避免使用兩種形式。
## 案例:開發字符集過濾器
主要任務,解決開發過程中的中文亂碼問題。

在進行開發的時候,如果存在中文,在每一個有中文內容的servlet中都需要進行字符編碼的設置,這對我們來說是重復的工作,所以我們可以借助過濾器實現對每個請求與響應的編碼設置。
~~~
@WebFilter(filterName="CharacterEncodingFilter",urlPatterns="/*")
public class CharacterEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//注意此處不是我們之前使用的HttpServletRequest和HttpServletResponse
HttpServletRequest req = (HttpServletRequest)request;
req.setCharacterEncoding("UTF-8");
HttpServletResponse res = (HttpServletResponse)response;
res.setContentType("text/html;charset=UTF-8");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
~~~
或采用
~~~
xml配置:
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.dodoke.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
~~~

**為什么doFilter使用的是ServletRequest而不是HttpServletRequest?**
過濾器過濾的不是只有web頁面的請求和響應,同時也需要過濾其他類型的請求和響應,其中涉及J2EE的底層機制。
ServletRequest接口是所有類型請求的最頂層的接口,包含了所有請求接口的通用方法。HttpServletRequest接口是其中的一個子接口,擴展定義自己的方法,是針對Http協議進行定義。
RequestFacade類是tomcat服務器針對HttpServletRequest接口的實現類,如果使用別的web服務器,實現類也就不是RequestFacade了。
J2EE是定制者,定制所有的接口,而實現類都是由三方廠商自己定制,此處servlet-api.jar就是J2EE的規范,catalina.jar是由Tomcat提供的實現類。響應接口同理。
## 過濾器開發技巧
**過濾器參數化**
* 過濾器為了增強靈活性,允許配置信息放在web.xml
* 在web.xml中配置<init-param>設置過濾器參數
~~~
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>com.dodoke.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
<init-param>
<param-name>p1</param-name>
<param-value>v1</param-value>
</init-param>
<init-param>
<param-name>p2</param-name>
<param-value>v2</param-value>
</init-param>
</filter>
~~~
~~~
@WebFilter(filterName="CharacterEncodingFilter",urlPatterns="/*",
initParams= {
@WebInitParam(name="encoding" , value="GBK"),
@WebInitParam(name="p1" , value="v1"),
@WebInitParam(name="p2" , value="v2")
})
public class CharacterEncodingFilter implements Filter {
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
encoding=filterConfig.getInitParameter("encoding");
System.out.println("encoding:"+encoding);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//注意此處不是我們之前使用的HttpServletRequest和HttpServletResponse
HttpServletRequest req = (HttpServletRequest)request;
req.setCharacterEncoding("UTF-8");
HttpServletResponse res = (HttpServletResponse)response;
res.setContentType("text/html;charset=UTF-8");
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
~~~
## url-pattern設置過濾范圍
**url-pattern常用寫法:**
* `/index.jsp` - 執行資源精準匹配
* `/servlet/*`-以前綴進行模糊匹配(匹配以url中以servlet開頭的)
* `*.jsp`-以后綴進行模糊匹配
url-pattern允許組合使用形成組合鏈。
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
I'm index page!
</body>
</html>
~~~
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
I'm test page!
</body>
</html>
~~~
~~~
@WebServlet("/servlet/sample1")
public class SampleServlet1 extends HttpServlet {
private static final long serialVersionUID = 1L;
public SampleServlet1() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().println("I'm " + this.getClass().getSimpleName());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
~~~
~~~
@WebServlet("/")
public class SampleServlet2 extends HttpServlet {
private static final long serialVersionUID = 1L;
public SampleServlet2() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().println("I'm " + this.getClass().getSimpleName());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
~~~
~~~
/*@WebFilter(filterName="UrlPatternFilter" , urlPatterns= {
"/","/servlet/*","*.jsp"
})*/
//復雜url,還是使用配置比較好
public class UrlPatternFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
System.out.println("攔截到:" + req.getRequestURL());
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
~~~
~~~
<?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>url-pattern</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>UrlPatternFilter</filter-name>
<filter-class>com.imooc.filter.UrlPatternFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>UrlPatternFilter</filter-name>
<url-pattern>/</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>UrlPatternFilter</filter-name>
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>UrlPatternFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
</web-app>
~~~
**映射的問題:**
* /指映射Web應用根路徑,且只對Servlet生效
* 默認首頁index.jsp會讓/失效
* /與/*含義不同,前者指向根路徑,后者代表所有
## 過濾鏈
**過濾鏈開發注意事項:**
* 每一個過濾器應具有單獨職能
* 調用chain.doFiiter()將請求向后傳遞
* 過濾鏈是雙向的,響應返回時按逆序通過過濾鏈
* 過濾器的執行順序以XML配置中的<filter-mapping>從上到下執行
* 以注解形式設置過濾器時,過濾器執行順序按過濾器類名(不是注解中的filterName)的大小順序進行,如:filterA filterZ filterV三個過濾器執行順序為AVZ(不合理)類名有獨特含義,不能特地為了過濾鏈的執行順序而修改過濾器類名
## 案例:多端設備自動匹配
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>這是客戶端頁面</h1>
</body>
</html>
~~~
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>這是移動端頁面</h1>
</body>
</html>
~~~
~~~
public class DeviceAdapterFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
/*原理:將不同端的頁面放置在不同的文件夾中,判斷請求的路徑,增加前綴
/index.html
PC: /desktop/index.html
MOBILE: /mobile/index.html
/test.html
PC: /desktop/test.html
MOBILE: /mobile/test.html
*/
//注意區分url和uri
String uri = req.getRequestURI();
System.out.println("URI:" + uri);
if(uri.startsWith("/desktop") || uri.startsWith("/mobile")) {
chain.doFilter(request, response);
}else {
//獲取請求主體,并全部轉為小寫
String userAgent = req.getHeader("user-agent").toLowerCase();
String targetURI="";
if(userAgent.indexOf("android")!=-1 || userAgent.indexOf("iphone") != -1) {
targetURI = "/mobile" + uri;
System.out.println("移動端設備正在訪問,重新跳轉URI:" + targetURI);
//此處將原始URI修改成targetURI后,不進入過濾鏈。
//而是利用響應重定向senRedirect(targetURI)使瀏覽器重新發送請求,此時請求便能通過過濾器。
res.sendRedirect(targetURI);
}else {
targetURI = "/desktop" + uri;
System.out.println("PC端設備正在訪問,重新跳轉URI:" + targetURI);
res.sendRedirect(targetURI);
}
}
}
@Override
public void destroy() {
}
}
~~~
~~~
<?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>device-adapter</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>DeviceAdapterFilter</filter-name>
<filter-class>com.dodoke.filter.DeviceAdapterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>DeviceAdapterFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
</web-app>
~~~