Servlet中的過濾器Filter是實現了javax.servlet.Filter接口的服務器端程序,主要的用途是過濾字符編碼、做一些業務邏輯判斷等。其工作原理是,只要你在web.xml文件配置好要攔截的客戶端請求,它都會幫你攔截到請求,此時你就可以對請求或響應(Request、Response)統一設置編碼,簡化操作;同時還可進行邏輯判斷,如用戶是否已經登陸、有沒有權限訪問該頁面等等工作。它是隨你的web應用啟動而啟動的,只初始化一次,以后就可以攔截相關請求,只有當你的web應用停止或重新部署的時候才銷毀。
簡單來說,過濾器filter的作用,它就相當於公路上的一個關卡,對要通過的攔截下來進行相關操作或放行。
dofilter作用【request -> filter1 -> filter2 ->filter3 -> .... -> request resource。】
看一個簡單的沒有filter的登錄例子,當用戶名和密碼都是admin時,能跳轉到success頁面,否則到fail頁面。
1、eclipse建立一個web project ,結果目錄如下
其中,jsp很簡單。在login.jsp為一個form表單
1 <form action="<%=request.getContextPath() %>/servlet/LoginServlet" method="post"> 2 用戶名:<input type="text" name="username"> 3 密碼:<input type="password" name="password"> 4 <input type="submit" value="提交"> 5 </form>
配置文件xml中為
1 <servlet> 2 <description>This is the description of my J2EE component</description> 3 <display-name>This is the display name of my J2EE component</display-name> 4 <servlet-name>LoginServlet</servlet-name> 5 <servlet-class>com.imooc.serlvet.LoginServlet</servlet-class> 6 </servlet> 7 8 <servlet-mapping> 9 <servlet-name>LoginServlet</servlet-name> 10 <url-pattern>/servlet/LoginServlet</url-pattern> 11 </servlet-mapping>
LoginServlet.java中主要是
1 public void destroy() { 2 super.destroy(); // Just puts "destroy" string in log 3 // Put your code here 4 } 5 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 6 String username = request.getParameter("username"); 7 String password = request.getParameter("password"); 8 9 System.out.println(username); 10 11 if("admin".equals(username) && "admin".equals(password)){ 12 //校驗通過 13 HttpSession session = request.getSession(); 14 session.setAttribute("username", username); 15 response.sendRedirect(request.getContextPath()+"/sucess.jsp"); 16 }else{ 17 //校驗失敗 18 response.sendRedirect(request.getContextPath()+"/fail.jsp"); 19 } 20 21 } 22 public void init() throws ServletException { 23 // Put your code here 24 }
運行后瀏覽器地址欄進入:...8080/LoginFilter/login.jsp
當用戶名和密碼都輸入admin,按下submit,就會進入action對應的servlet,然后重定向到success.jsp頁面;用戶名密碼不對就到fail.jsp。
小例子結束。
仔細想會想到,可以直接在地址輸入8080/LoginFilter/success.jsp不就進入了成功的頁面了嗎,還有類似頁面。這樣時不安全的,所以要加入攔截器
2、在此項目加入攔截器filter,也就是對其url進行攔截,不讓其用url隨便訪問,結構中多了個類文件
在xml中添加
1 <filter> 2 <filter-name>LoginFilter</filter-name> 3 <filter-class>com.imooc.filter.LoginFilter</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>LoginFilter</filter-name> 7 <url-pattern>/success.jsp</url-pattern> 8 </filter-mapping>
這樣,url輸入...8080/LoginFilter/success.jsp就會進行攔截,進入LoginFilter.java
里面主要為:
1 @Override 2 public void destroy() { 3 System.out.println("銷毀..."); 4 } 5 @Override 6 public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { 7 HttpServletRequest request = (HttpServletRequest) arg0; 8 HttpServletResponse response = (HttpServletResponse) arg1; 9 HttpSession session = request.getSession(); 10 //因為登錄后保存了username,所以可以先檢查username判斷是否登錄 11 if(session.getAttribute("username")!=null){ 12 arg2.doFilter(arg0, arg1);//已登錄,則放行, 13 }else{ 14 response.sendRedirect("login.jsp");//未登錄,重定向到登錄頁面 15 } 16 } 17 public void init(FilterConfig arg0) throws ServletException 18 { 19 20 System.out.println("初始化..."); 21 22 }
【過濾器Filter生命周期:init()->doFilter()->destroy()】
【 出現了 arg.doFailter(arg0,arg1),他表示好像放行、通過這樣,繼續往下到LoginServlet.java中,如果沒有寫 arg.doFailter(arg0,arg1)。那就是,把他攔截下來后,什么都不做,也不讓它通過,那就會卡在那里不能往下了。】
【在這里只是request -> filter1 -> request resource。】
這樣的思路,就可以攔截一些不能隨便訪問的頁面,但如果這類頁面很多,可訪問的頁面相對少,則可以把攔截的地址改為/*,也就是
1 <filter-mapping> 2 <filter-name>LoginFilter</filter-name> 3 <url-pattern>/success.jsp</url-pattern> 4 </filter-mapping> 5 改成 6 <filter-mapping> 7 <filter-name>LoginFilter</filter-name> 8 <url-pattern>/*</url-pattern> 9 </filter-mapping>
再對本不用攔截的,比如login.jsp 在LoginFilter進行判斷
1 public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { 2 HttpServletRequest request = (HttpServletRequest) arg0; 3 HttpServletResponse response = (HttpServletResponse) arg1; 4 HttpSession session = request.getSession(); 5 //如果是login.jsp,則不攔截,直接放行,不用進行其他操作 6 if(request.getRequestURI().indexOf("login.jsp")!=-1 ){ 7 arg2.doFilter(arg0, arg1); 8 return; 9 } 10 11 //因為登錄后保存了username,所以可以先檢查username判斷是否登錄 12 if(session.getAttribute("username")!=null){ 13 arg2.doFilter(arg0, arg1);//已登錄,則放行, 14 }else{ 15 response.sendRedirect("login.jsp");//未登錄,重定向到登錄頁面 16 }
仔細想想,其他問題也來了,因為攔截所有頁面,所以按下submit時都過不去(action="<%=request.getContextPath() %>/servlet/LoginServlet" 也要攔截,過不去),則要添加一些不需攔截的url
1 //如果是下面3個url,則不攔截,直接放行,不用進行其他操作 2 if(request.getRequestURI().indexOf("login.jsp")!=-1 3 ||request.getRequestURI().indexOf("servlet/LoginServlet")!=-1 4 ||request.getRequestURI().indexOf("fail.jsp")!=-1 5 ){ 6 arg2.doFilter(arg0, arg1); 7 return; 8 }
但這樣也有問題,當不需攔截的url多了,if語句也屢屢需要修改,很麻煩,則可以用 FilterConfig對象。先在xml添加配置,有新的不需要攔截的url,秩序在配置里添加即可。
1 <filter> 2 <filter-name>LoginFilter</filter-name> 3 <filter-class>com.imooc.filter.LoginFilter</filter-class> 4 <init-param> 5 <param-name>noLoginPaths</param-name> 6 <param-value>login.jsp;fail.jsp;LoginServlet</param-value> //在此添加不需攔截的url 7 </init-param> 8 <init-param> 9 <param-name>charset</param-name> //防止中文亂碼 10 <param-value>UTF-8</param-value> 11 </init-param> 12 </filter> 13 <filter-mapping> 14 <filter-name>LoginFilter</filter-name> 15 <url-pattern>/*</url-pattern> 16 </filter-mapping>
在dofilter中得到此對象 LoginFilter.java
1 public class LoginFilter implements Filter { 2 private FilterConfig config; //這里定義一下 3 @Override 4 public void destroy() { 5 6 } 7 @Override 8 public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { 9 10 HttpServletRequest request = (HttpServletRequest) arg0; 11 HttpServletResponse response = (HttpServletResponse) arg1; 12 HttpSession session = request.getSession(); 13 String noLoginPaths = config.getInitParameter("noLoginPaths"); //已獲取filterconfig對象,可以獲取其中屬性 14 15 String charset = config.getInitParameter("charset"); 16 if(charset==null){ 17 charset = "UTF-8"; 18 } 19 request.setCharacterEncoding(charset); 20 21 if(noLoginPaths!=null){ 22 String[] strArray = noLoginPaths.split(";"); //對屬性的值分割。分別放行 23 for (int i = 0; i < strArray.length; i++) { 24 if(strArray[i]==null || "".equals(strArray[i]))continue; 25 if(request.getRequestURI().indexOf(strArray[i])!=-1 ){ 26 arg2.doFilter(arg0, arg1); 27 return; 28 } 29 } 30 } 31 if(session.getAttribute("username")!=null){ 32 arg2.doFilter(arg0, arg1); 33 }else{ 34 response.sendRedirect("login.jsp"); 35 } 36 } 37 @Override 38 public void init(FilterConfig arg0) throws ServletException { 39 config = arg0; //在初始化時把次對象賦值 40 } 41 42 }
//執行順序:最先初始化init(),然后dofilter函數 最后destory()
這里,這個登錄案例就完成了。
那【request -> filter1 -> filter2 ->filter3 -> .... -> request resource。】是什么呢
就是一個url-partten對應了多個filter,一個url,被攔截了好幾次。
1 <filter> 2 <filter-name>FirstFilter</filter-name> 3 <filter-class>com.imooc.filter.FirstFilter</filter-class> 4 </filter> 5 <filter> 6 <filter-name>SecondFilter</filter-name> 7 <filter-class>com.imooc.filter.SecondFilter</filter-class> 8 </filter> 9 10 <filter-mapping> 11 <filter-name>FirstFilter</filter-name> 12 <url-pattern>/index.jsp</url-pattern> 13 </filter-mapping> 14 <filter-mapping> 15 <filter-name>SecondFilter</filter-name> 16 <url-pattern>/index.jsp</url-pattern> 17 </filter-mapping>
這樣就存在一個過濾器鏈,按xml中順序執行。
index.jsp
1 <body> 2 This is my JSP page. <br> 3 <% 4 System.out.println("到了index.jsp"); 5 %> 6 </body>
FirstFilter.java
1 public class FirstFilter implements Filter { 2 3 @Override 4 public void destroy() { 5 System.out.println("destroy---FirstFilter"); 6 } 7 8 @Override 9 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 10 System.out.println("start----doFilter--FirstFilter"); 11 chain.doFilter(request, response); 12 HttpServletRequest req =(HttpServletRequest) request; 13 HttpServletResponse response2 =(HttpServletResponse) response; 19 System.out.println("end------doFilter--FirstFilter"); 20 } 21 22 @Override 23 public void init(FilterConfig filterConfig) throws ServletException { 24 System.out.println("init----FirstFilter"); 25 } 26 27 28 }
SecondFilter.java
1 public class SecondFilter implements Filter { 2 3 @Override 4 public void destroy() { 5 System.out.println("destroy-----SecondFilter"); 6 } 7 8 @Override 9 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 10 System.out.println("start---doFilter--SecondFilter"); 11 chain.doFilter(request, response); 12 System.out.println("end---doFilter--SecondFilter"); 13 } 14 15 @Override 16 public void init(FilterConfig filterConfig) throws ServletException { 17 18 System.out.println("init-----SecondFilter"); 19 } 20 21 }
過濾器鏈具體的執行順序:
在運行項目時,兩個filter類都執行了init()初始化,控制台輸出:
init-----FirstFilter
init-----SecondFilter
當訪問index.jsp時,進入FirstFilter.java的dofilter函數,控制台會依次輸出:
start---doFilter--FirstFilter
start---doFilter--SecondFilter
到了index.jsp
end---doFilter--FirstFilter
end---doFilter--SecondFilter
在dofilter函數中,先執行chain.doFilter(request, response);前的代碼,然后下一個filter鏈的下一個filter,然后進入index.jsp。再繼續依次執行chain.doFilter(request, response);后面的代碼
Code1表示ilter(request, response);之前的代碼Code2表示ilter(request, response);之后的代碼。
文章為學習記錄,若有錯誤,望指正。