過濾器(Filter)


day21

 

過濾器概述

 

1 什么是過濾器

過濾器JavaWeb三大組件之一,它與Servlet很相似!不它過濾器是用來攔截請求的,而不是處理請求的。

當用戶請求某個Servlet時,會先執行部署在這個請求上的Filter,如果Filter“放行”,那么會繼承執行用戶請求的Servlet;如果Filter不“放行”,那么就不會執行用戶請求的Servlet。

其實可以這樣理解,當用戶請求某個Servlet時,Tomcat會去執行注冊在這個請求上的Filter,然后是否“放行”由Filter來決定。可以理解為,Filter來決定是否調用Servlet!當執行完成Servlet的代碼后,還會執行Filter后面的代碼。

 

 

2 過濾器之hello world

  其實過濾器與Servlet很相似,我們回憶一下如果寫的第一個Servlet應用!寫一個類,實現Servlet接口!沒錯,寫過濾器就是寫一個類,實現Filter接口。

public class HelloFilter implements Filter {

    public void init[崔1] (FilterConfig filterConfig) throws ServletException {}

    public void doFilter[崔2] (ServletRequest request, ServletResponse response,

           FilterChain chain) throws IOException, ServletException {

       System.out.println("Hello Filter");

    }

    public void destroy[崔3] () {}

}

 

第二步也與Servlet一樣,在web.xml文件中部署Filter:

  <filter>

    <filter-name>helloFilter</filter-name>

    <filter-class>cn.itcast.filter.HelloFilter</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>helloFilter</filter-name>

    <url-pattern>/index.jsp[崔4] </url-pattern>

  </filter-mapping>

 

應該沒有問題吧,都可以看懂吧!

OK了,現在可以嘗試去訪問index.jsp頁面了,看看是什么效果!

當用戶訪問index.jsp頁面時,會執行HelloFilter的doFilter()方法!在我們的示例中,index.jsp頁面是不會被執行的,如果想執行index.jsp頁面,那么我們需要放行!

    public void doFilter(ServletRequest request, ServletResponse response,

           FilterChain chain) throws IOException, ServletException {

       System.out.println("filter start...");[崔5] 

       chain.doFilter(request, response);[崔6] 

       System.out.println("filter end...");[崔7] 

    }

 

 

  有很多同學總是錯誤的認為,一個請求在給客戶端輸出之后就算是結束了,這是不對的!其實很多事情都需要在給客戶端響應之后才能完成!

 

過濾器詳細

 

1 過濾器的生命周期

我們已經學習過Servlet的生命周期,那么Filter的生命周期也就沒有什么難度了!

l  init(FilterConfig):在服務器啟動時會創建Filter實例,並且每個類型的Filter只創建一個實例,從此不再創建!在創建完Filter實例后,會馬上調用init()方法完成初始化工作,這個方法只會被執行一次;

l  doFilter(ServletRequest req,ServletResponse res,FilterChain chain):這個方法會在用戶每次訪問“目標資源(<url->pattern>index.jsp</url-pattern>)”時執行,如果需要“放行”,那么需要調用FilterChain的doFilter(ServletRequest,ServletResponse)方法,如果不調用FilterChain的doFilter()方法,那么目標資源將無法執行;

l  destroy():服務器會在創建Filter對象之后,把Filter放到緩存中一直使用,通常不會銷毀它。一般會在服務器關閉時銷毀Filter對象,在銷毀Filter對象之前,服務器會調用Filter對象的destory()方法。

 

2 FilterConfig

你已經看到了吧,Filter接口中的init()方法的參數類型為FilterConfig類型。它的功能與ServletConfig相似,與web.xml文件中的配置信息對應。下面是FilterConfig的功能介紹:

l  ServletContext getServletContext():獲取ServletContext的方法;

l  String getFilterName():獲取Filter的配置名稱;與<filter-name>元素對應;

l  String getInitParameter(String name):獲取Filter的初始化配置,與<init-param>元素對應;

l  Enumeration getInitParameterNames():獲取所有初始化參數的名稱。

 

 

 

3 FilterChain

doFilter()方法的參數中有一個類型為FilterChain的參數,它只有一個方法:doFilter(ServletRequest,ServletResponse)。

前面我們說doFilter()方法的放行,讓請求流訪問目標資源!但這么說不嚴密,其實調用該方法的意思是,“我(當前Filter)”放行了,但不代表其他人(其他過濾器)也放行。

也就是說,一個目標資源上,可能部署了多個過濾器,就好比在你去北京的路上有多個打劫的匪人(過濾器),而其中第一伙匪人放行了,但不代表第二伙匪人也放行了,所以調用FilterChain類的doFilter()方法表示的是執行下一個過濾器的doFilter()方法,或者是執行目標資源!

如果當前過濾器是最后一個過濾器,那么調用chain.doFilter()方法表示執行目標資源,而不是最后一個過濾器,那么chain.doFilter()表示執行下一個過濾器的doFilter()方法。

 

4 多個過濾器執行順序

一個目標資源可以指定多個過濾器,過濾器的執行順序是在web.xml文件中的部署順序:

  <filter>

    <filter-name>myFilter1[崔8] </filter-name>

    <filter-class>cn.itcast.filter.MyFilter1</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>myFilter1</filter-name>

    <url-pattern>/index.jsp</url-pattern>

  </filter-mapping>

  <filter>

    <filter-name>myFilter2</filter-name>

    <filter-class>cn.itcast.filter.MyFilter2</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>myFilter2</filter-name>

    <url-pattern>/index.jsp</url-pattern>

  </filter-mapping>

public class MyFilter1 extends HttpFilter {

    public void doFilter(HttpServletRequest request, HttpServletResponse response,

           FilterChain chain) throws IOException, ServletException {

       System.out.println("filter1 start...");

       chain.doFilter(request, response);//放行,執行MyFilter2的doFilter()方法

       System.out.println("filter1 end...");

    }

}

public class MyFilter2 extends HttpFilter {

    public void doFilter(HttpServletRequest request, HttpServletResponse response,

           FilterChain chain) throws IOException, ServletException {

       System.out.println("filter2 start...");

       chain.doFilter(request, response);//放行,執行目標資源

       System.out.println("filter2 end...");

    }

}

  <body>

    This is my JSP page. <br>

    <h1>index.jsp</h1>

    <%System.out.println("index.jsp"); %>

  </body>

 

當有用戶訪問index.jsp頁面時,輸出結果如下:

filter1 start...

filter2 start...

index.jsp

filter2 end...

filter1 end...

 

5 四種攔截方式

我們來做個測試,寫一個過濾器,指定過濾的資源為b.jsp,然后我們在瀏覽器中直接訪問b.jsp,你會發現過濾器執行了!

但是,當我們在a.jsp中request.getRequestDispathcer(“/b.jsp”).forward(request,response)時,就不會再執行過濾器了!也就是說,默認情況下,只能直接訪問目標資源才會執行過濾器,而forward執行目標資源,不會執行過濾器!

public class MyFilter extends HttpFilter {

    public void doFilter(HttpServletRequest request,

           HttpServletResponse response, FilterChain chain)

           throws IOException, ServletException {

       System.out.println("myfilter...");

       chain.doFilter(request, response);

    }

}  

    <filter>

       <filter-name>myfilter</filter-name>

       <filter-class>cn.itcast.filter.MyFilter</filter-class>

    </filter>

    <filter-mapping>

       <filter-name>myfilter</filter-name>

       <url-pattern>/b.jsp</url-pattern>

    </filter-mapping>

  <body>

   <h1>b.jsp</h1>

  </body>

  <h1>a.jsp</h1>

    <%

        request.getRequestDispatcher("/b.jsp").forward(request, response);

    %>

  </body>

 

http://localhost:8080/filtertest/b.jsp -->直接訪問b.jsp時,會執行過濾器內容;

http://localhost:8080/filtertest/a.jsp --> 訪問a.jsp,但a.jsp會forward到b.jsp,這時就不會執行過濾器!

 

其實過濾器有四種攔截方式!分別是:REQUEST、FORWARD、INCLUDE、ERROR。

l  REQUEST:直接訪問目標資源時執行過濾器。包括:在地址欄中直接訪問、表單提交、超鏈接、重定向,只要在地址欄中可以看到目標資源的路徑,就是REQUEST;

l  FORWARD:轉發訪問執行過濾器。包括RequestDispatcher#forward()方法、<jsp:forward>標簽都是轉發訪問;

l  INCLUDE:包含訪問執行過濾器。包括RequestDispatcher#include()方法、<jsp:include>標簽都是包含訪問;

l  ERROR:當目標資源在web.xml中配置為<error-page>中時,並且真的出現了異常,轉發到目標資源時,會執行過濾器。

 

可以在<filter-mapping>中添加0~n個<dispatcher>子元素,來說明當前訪問的攔截方式。

    <filter-mapping>

       <filter-name>myfilter</filter-name>

       <url-pattern>/b.jsp</url-pattern>

       <dispatcher>REQUEST</dispatcher>[崔9] 

       <dispatcher>FORWARD</dispatcher>[崔10] 

    </filter-mapping>

    <filter-mapping>

       <filter-name>myfilter</filter-name>

       <url-pattern>/b.jsp</url-pattern>

    </filter-mapping>[崔11] 

    <filter-mapping>

       <filter-name>myfilter</filter-name>

       <url-pattern>/b.jsp</url-pattern>

       <dispatcher>FORWARD</dispatcher>[崔12] 

    </filter-mapping>

 

其實最為常用的就是REQUEST和FORWARD兩種攔截方式,而INCLUDE和ERROR都比較少用!其中INCLUDE比較好理解,我們這里不再給出代碼,學員可以通過FORWARD方式修改,來自己測試。而ERROR方式不易理解,下面給出ERROR攔截方式的例子:

    <filter-mapping>

       <filter-name>myfilter</filter-name>

       <url-pattern>/b.jsp</url-pattern>

       <dispatcher>ERROR</dispatcher>[崔13] 

    </filter-mapping>

    <error-page>

       <error-code>500</error-code>

       <location>/b.jsp</location>[崔14] 

    </error-page>

  <body>

  <h1>a.jsp</h1>

   <%

   if(true)

   throw new RuntimeException("嘻嘻~");[崔15] 

   %>

  </body>

 

6 過濾器的應用場景

過濾器的應用場景:

l  執行目標資源之前做預處理工作,例如設置編碼,這種試通常都會放行,只是在目標資源執行之前做一些准備工作;[c16] 

l  通過條件判斷是否放行,例如校驗當前用戶是否已經登錄,或者用戶IP是否已經被禁用;

l  在目標資源執行后,做一些后續的特殊處理工作,例如把目標資源輸出的數據進行處理[c17] ;

 

7 設置目標資源

在web.xml文件中部署Filter時,可以通過“*”來執行目標資源:

    <filter-mapping>

       <filter-name>myfilter</filter-name>

       <url-pattern>/*</url-pattern>[崔18] 

    </filter-mapping>

 

這一特性與Servlet完全相同!通過這一特性,我們可以在用戶訪問敏感資源時,執行過濾器,例如:<url-pattern>/admin/*<url-pattern>,可以把所有管理員才能訪問的資源放到/admin路徑下,這時可以通過過濾器來校驗用戶身份。

還可以為<filter-mapping>指定目標資源為某個Servlet,例如:

    <servlet>

       <servlet-name>myservlet</servlet-name>

       <servlet-class>cn.itcast.servlet.MyServlet</servlet-class>

    </servlet>

    <servlet-mapping>

       <servlet-name>myservlet</servlet-name>

       <url-pattern>/abc</url-pattern>

    </servlet-mapping>

    <filter>

       <filter-name>myfilter</filter-name>

       <filter-class>cn.itcast.filter.MyFilter</filter-class>

    </filter>

    <filter-mapping>

       <filter-name>myfilter</filter-name>

       <servlet-name>myservlet</servlet-name>[崔19] 

    </filter-mapping>

 

  當用戶訪問http://localhost:8080/filtertest/abc時,會執行名字為myservlet的Servlet,這時會執行過濾器。

 

8 Filter小結

Filter的三個方法:

l  void init(FilterConfig):在Tomcat啟動時被調用;

l  void destroy():在Tomcat關閉時被調用;

l  void doFilter(ServletRequest,ServletResponse,FilterChain):每次有請求時都調用該方法;

 

FilterConfig類:與ServletConfig相似,用來獲取Filter的初始化參數

l  ServletContext getServletContext():獲取ServletContext的方法;

l  String getFilterName():獲取Filter的配置名稱;

l  String getInitParameter(String name):獲取Filter的初始化配置,與<init-param>元素對應;

l  Enumeration getInitParameterNames():獲取所有初始化參數的名稱。

 

FilterChain類:

l  void doFilter(ServletRequest,ServletResponse):放行!表示執行下一個過濾器,或者執行目標資源。可以在調用FilterChain的doFilter()方法的前后添加語句,在FilterChain的doFilter()方法之前的語句會在目標資源執行之前執行,在FilterChain的doFilter()方法之后的語句會在目標資源執行之后執行。

 

四各攔截方式:REQUEST、FORWARD、INCLUDE、ERROR,默認是REQUEST方式。

l  REQUEST:攔截直接請求方式;

l  FORWARD:攔截請求轉發方式;

l  INCLUDE:攔截請求包含方式;

l  ERROR:攔截錯誤轉發方式。

 

過濾器應用案例

分ip統計網站的訪問次數

ip

count

192.168.1.111

2

192.168.1.112

59

 

統計工作需要在所有資源之前都執行,那么就可以放到Filter中了。

我們這個過濾器不打算做攔截操作!因為我們只是用來做統計的。

用什么東西來裝載統計的數據。Map<String,Integer>

整個網站只需要一個Map即可!

Map什么時候創建(使用ServletContextListener,在服務器啟動時完成創建,並只在到ServletContext中),Map保存到哪里!(Map保存到ServletContext中!!!)

  • Map需要在Filter中用來保存數據
  • Map需要在頁面使用,打印Map中的數據

 

 

 

 

1 說明

  網站統計每個IP地址訪問本網站的次數。

 

2 分析

因為一個網站可能有多個頁面,無論哪個頁面被訪問,都要統計訪問次數,所以使用過濾器最為方便。

因為需要分IP統計,所以可以在過濾器中創建一個Map,使用IP為key,訪問次數為value。當有用戶訪問時,獲取請求的IP,如果IP在Map中存在,說明以前訪問過,那么在訪問次數上加1,即可;IP在Map中不存在,那么設置次數為1。

把這個Map存放到ServletContext中!

 

3 代碼

index.jsp

  <body>

<h1>分IP統計訪問次數</h1>

<table align="center" width="50%" border="1">

    <tr>

       <th>IP地址</th>

       <th>次數</th>

    </tr>

<c:forEach items="${applicationScope.ipCountMap }" var="entry">

    <tr>

       <td>${entry.key }</td>

       <td>${entry.value }</td>

    </tr>

</c:forEach>

[崔20] </table>

  </body>

 

IPFilter

public class IPFilter implements Filter {

    private ServletContext context;

 

    public void init(FilterConfig fConfig) throws ServletException {

       context = fConfig.getServletContext();[崔21] 

       Map<String, Integer> ipCountMap = Collections

              .synchronizedMap(new LinkedHashMap<String, Integer>());

[崔22]     context.setAttribute("ipCountMap", ipCountMap);

    }

 

    @SuppressWarnings("unchecked")

    public void doFilter(ServletRequest request, ServletResponse response,

           FilterChain chain) throws IOException, ServletException {

       HttpServletRequest req = (HttpServletRequest) request;

       String ip = req.getRemoteAddr();[崔23] 

 

       Map<String, Integer> ipCountMap = (Map<String, Integer>) context

              .getAttribute("ipCountMap");

[崔24] 

       Integer count = ipCountMap.get(ip);[崔25] 

       if (count == null) {

           count = 1;

[崔26]     } else {

           count += 1;[崔27] 

       }

       ipCountMap.put(ip, count);[崔28] 

 

       context.setAttribute("ipCountMap", ipCountMap);[崔29] 

       chain.doFilter(request, response);[崔30] 

    }

 

    public void destroy() {}

}

  <filter>

    <display-name>IPFilter</display-name>

    <filter-name>IPFilter</filter-name>

    <filter-class>cn.itcast.filter.ip.IPFilter</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>IPFilter</filter-name>

    <url-pattern>/*</url-pattern>

  </filter-mapping>

 

粗粒度權限控制(攔截是否登錄、攔截用戶名admin權限)

RBAC à 基於角色的權限控制

l  tb_user

l  tb_role

l  tb_userrole

l  tb_menu(增、刪、改、查)

l  tb_rolemenu

1 說明

我們給出三個頁面:index.jsp、user.jsp、admin.jsp。

l  index.jsp:誰都可以訪問,沒有限制;

l  user.jsp:只有登錄用戶才能訪問;

l  admin.jsp:只有管理員才能訪問。

 

2 分析

設計User類:username、password、grade,其中grade表示用戶等級,1表示普通用戶,2表示管理員用戶。

當用戶登錄成功后,把user保存到session中。

創建LoginFilter,它有兩種過濾方式:

l  如果訪問的是user.jsp,查看session中是否存在user;

l  如果訪問的是admin.jsp,查看session中是否存在user,並且user的grade等於2。

 

3 代碼

User.java

public class User {

    private String username;

    private String password;

    private int grade[崔31] ;

}

 

為了方便,這里就不使用數據庫了,所以我們需要在UserService中創建一個Map,用來保存所有用戶。Map中的key中用戶名,value為User對象。

UserService.java

public class UserService {

    private static Map<String,User> users [崔32] = new HashMap<String,User>();

    static {

       users.put("zhangSan", new User("zhangSan", "123", 1));

       users.put("liSi", new User("liSi", "123", 2));

[崔33]  }

   

    public User login[崔34] (String username, String password) {

       User user = users.get(username);[崔35] 

       if(user == null) return null;[崔36] 

       return user.getPassword().equals(password) ? user : null;[崔37] 

    }

}

 

login.jsp

  <body>

  <h1>登錄</h1>

    <p style="font-weight: 900; color: red">${msg }[崔38] </p>

    <form action="<c:url value='/LoginServlet'/>" method="post">

        用戶名:<input type="text" name="username"/><br/>

        密 碼:<input type="password" name="password"/><br/>

        <input type="submit" value="登錄"/>

    </form>

  </body>

 

index.jsp

  <body>

    <h1>主頁</h1>

    <h3>${user.username }</h3>

    <hr/>

    <a href="<c:url value='/login.jsp'/>">登錄</a><br/>

    <a href="<c:url value='/user/user.jsp'/>">用戶頁面</a><br/>

    <a href="<c:url value='/admin/admin.jsp'/>">管理員頁面</a>

  </body>

 

/user/user.jsp

<body>

<h1>用戶頁面</h1>

<h3>${user.username }</h3>

<hr/>

</body>

 

/admin/admin.jsp

<body>

  <h1>管理員頁面</h1>

  <h3>${user.username }</h3>

  <hr/>

</body>

 

LoginServlet

public class LoginServlet extends HttpServlet {

    public void doPost(HttpServletRequest request, HttpServletResponse response)

           throws ServletException, IOException {

       request.setCharacterEncoding("utf-8");

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

      

       String username = request.getParameter("username");

       String password = request.getParameter("password");

[崔39]     UserService userService = new UserService();

       User user = userService.login(username, password);[崔40] 

       if(user == null[崔41] ) {

           request.setAttribute("msg", "用戶名或密碼錯誤");

           request.getRequestDispatcher("/login.jsp").forward(request, response);

[崔42]     } else {

           request.getSession().setAttribute("user", user);

           request.getRequestDispatcher("/index.jsp").forward(request, response);

[崔43]     }

    }

}

 

LoginUserFilter.java

  <filter>

    <display-name>LoginUserFilter</display-name>

    <filter-name>LoginUserFilter</filter-name>

    <filter-class>cn.itcast.filter.LoginUserFilter</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>LoginUserFilter</filter-name>

    <url-pattern>/user/*[崔44] </url-pattern>

  </filter-mapping>

public class LoginUserFilter implements Filter {

    public void destroy() {}

    public void init(FilterConfig fConfig) throws ServletException {}

 

    public void doFilter(ServletRequest request, ServletResponse response,

           FilterChain chain) throws IOException, ServletException {

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

       HttpServletRequest req = (HttpServletRequest) request;

       User user = (User) req.getSession().getAttribute("user");[崔45] 

       if(user == null)[崔46]  {

           response.getWriter().print("您還沒有登錄");[崔47] 

           return;[崔48] 

       }

       chain.doFilter(request, response);[崔49] 

    }

}

 

LoginAdminFilter.java

  <filter>

    <display-name>LoginAdminFilter</display-name>

    <filter-name>LoginAdminFilter</filter-name>

    <filter-class>cn.itcast.filter.LoginAdminFilter</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>LoginAdminFilter</filter-name>

    <url-pattern>/admin/*[崔50] </url-pattern>

  </filter-mapping>

public class LoginAdminFilter implements Filter {

    public void destroy() {}

    public void init(FilterConfig fConfig) throws ServletException {}

 

    public void doFilter(ServletRequest request, ServletResponse response,

           FilterChain chain) throws IOException, ServletException {

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

       HttpServletRequest req = (HttpServletRequest) request;

       User user = (User) req.getSession().getAttribute("user");[崔51] 

       if(user == null) {

           response.getWriter().print("您還沒有登錄!");

           return;

       }[崔52] 

       if(user.getGrade() < 2) {

           response.getWriter().print("您的等級不夠!");

           return;

       }[崔53] 

       chain.doFilter(request, response);[崔54] 

    }

}

 

禁用資源緩存

瀏覽器只是要緩存頁面,這對我們在開發時測試很不方便,所以我們可以過濾所有資源,然后添加去除所有緩存!

public class NoCacheFilter extends HttpFilter {

    public void doFilter(HttpServletRequest request,

           HttpServletResponse response, FilterChain chain)

           throws IOException, ServletException {

       response.setHeader("cache-control", "no-cache");

       response.setHeader("pragma", "no-cache");

       response.setHeader("expires", "0");

       chain.doFilter(request, response);

    }

}

 

但是要注意,有的瀏覽器可能不會理會你的設置,還是會緩存的!這時就要在頁面中使用時間戳來處理了。

 

解決全站字符亂碼(POST和GET中文編碼問題)

 

servlet:

l  POST:request.setCharacterEncoding(“utf-8”);

l  GET:

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

 

 

 

1 說明

亂碼問題:

l  獲取請求參數中的亂碼問題;

  • POST請求:request.setCharacterEncoding(“utf-8”);
  • GET請求:new String(request.getParameter(“xxx”).getBytes(“iso-8859-1”), “utf-8”);

l  響應的亂碼問題:response.setContextType(“text/html;charset=utf-8”)。

 

  基本上在每個Servlet中都要處理亂碼問題,所以應該把這個工作放到過濾器中來完成。

 

2 分析

其實全站亂碼問題的難點就是處理GET請求參數的問題。

如果只是處理POST請求的編碼問題,以及響應編碼問題,那么這個過濾器就太!太!太簡單的。

public class EncodingFilter extends HttpFilter {

    public void doFilter(HttpServletRequest request,

           HttpServletResponse response, FilterChain chain)

           throws IOException, ServletException {

       String charset = this.getInitParameter("charset");[崔55] 

       if(charset == null || charset.isEmpty()) {

           charset = "UTF-8";

       }

[崔56]     request.setCharacterEncoding(charset);[崔57] 

       response.setContentType("text/html;charset=" + charset);[崔58] 

       chain.doFilter(request, response);

    }

}

 

如果是POST請求,當執行目標Servlet時,Servlet中調用request.getParameter()方法時,就會根據request.setCharacterEncoding()設置的編碼來轉碼!這說明在過濾器中調用request.setCharacterEncoding()方法會影響在目標Servlet中的request.getParameter()方法的行為!

但是如果是GET請求,我們又如何能影響request.getParameter()方法的行為呢?這是不好做到的!我們不可能先調用request.getParameter()方法獲取參數,然后手動轉碼后,再施加在到request中!因為request只有getParameter(),而沒有setParameter()方法。

 

處理GET請求參數編碼問題,需要在Filter中放行時,把request對象給“調包”了,也就是讓目標Servlet使用我們“調包”之后的request對象。這說明我們需要保證“調包”之后的request對象中所有方法都要與“調包”之前一樣可以使用,並且getParameter()方法還要有能力返回轉碼之后的參數。

 

這可能讓你想起了“繼承”,但是這里不能用繼承,而是“裝飾者模式(Decorator Pattern)”!

下面是三種對a對象進行增強的手段:

l  繼承:AA類繼承a對象的類型:A類,然后重寫fun1()方法,其中重寫的fun1()方法就是被增強的方法。但是,繼承必須要知道a對象的真實類型,然后才能去繼承。如果我們不知道a對象的確切類型,而只知道a對象是IA接口的實現類對象,那么就無法使用繼承來增強a對象了;

l  裝飾者模式:AA類去實現a對象相同的接口:IA接口,還需要給AA類傳遞a對象,然后在AA類中所有的方法實現都是通過代理a對象的相同方法完成的,只有fun1()方法在代理a對象相同方法的前后添加了一些內容,這就是對fun1()方法進行了增強;

l  動態代理:動態代理與裝飾者模式比較相似,而且是通過反射來完成的。動態代理會在最后一天的基礎加強中講解,這里就不再廢話了。

 

對request對象進行增強的條件,剛好符合裝飾者模式的特點!因為我們不知道request對象的具體類型,但我們知道request是HttpServletRequest接口的實現類。這說明我們寫一個類EncodingRequest,去實現HttpServletRequest接口,然后再把原來的request傳遞給EncodingRequest類!在EncodingRequest中對HttpServletRequest接口中的所有方法的實現都是通過代理原來的request對象來完成的,只有對getParameter()方法添加了增強代碼!

JavaEE已經給我們提供了一個HttpServletRequestWrapper類,它就是HttpServletRequest的包裝類,但它做任何的增強!你可能會說,寫一個裝飾類,但不做增強,其目的是什么呢?使用這個裝飾類的對象,和使用原有的request有什么分別呢?

HttpServletRequestWrapper類雖然是HttpServletRequest的裝飾類,但它不是用來直接使用的,而是用來讓我們去繼承的!當我們想寫一個裝飾類時,還要對所有不需要增強的方法做一次實現是很心煩的事情,但如果你去繼承HttpServletRequestWrapper類,那么就只需要重寫需要增強的方法即可了。

 

3 代碼

 

EncodingRequest

public class EncodingRequest extends HttpServletRequestWrapper [崔59] {

    private String charset;

    public EncodingRequest[崔60] (HttpServletRequest request, String charset) {

       super(request);

       this.charset = charset;

    }

 

    public String getParameter[崔61] (String name) {

       HttpServletRequest request [崔62] = (HttpServletRequest) getRequest();

      

       String method = request.getMethod()[崔63] ;

       if(method.equalsIgnoreCase("post[崔64] ")) {

           try {

              request.setCharacterEncoding(charset);[崔65] 

           } catch (UnsupportedEncodingException e) {}

       } else if(method.equalsIgnoreCase("get[崔66] ")) {

           String value = request.getParameter(name);[崔67] 

           try {

              value = new String(name.getBytes("ISO-8859-1"), charset);[崔68] 

           } catch (UnsupportedEncodingException e) {

           }

           return value[崔69] ;

       }

       return request.getParameter(name);

    }

}

 

EncodingFilter

public class EncodingFilter extends HttpFilter {

    public void doFilter(HttpServletRequest request,

           HttpServletResponse response, FilterChain chain)

           throws IOException, ServletException {

       String charset = this.getInitParameter("charset");[崔70] 

       if(charset == null || charset.isEmpty()) {

           charset = "UTF-8";

       }[崔71] 

       response.setCharacterEncoding(charset);

       response.setContentType("text/html;charset=" + charset);

       EncodingRequest res = new EncodingRequest(request, charset);[崔72] 

       chain.doFilter(res, response);[崔73] 

    }

}

 

web.xml

  <filter>

    <filter-name>EncodingFilter</filter-name>

    <filter-class>cn.itcast.filter.EncodingFilter</filter-class>

    <init-param>

       <param-name>charset</param-name>

       <param-value>UTF-8</param-value>

    </init-param>

  </filter>

  <filter-mapping>

    <filter-name>EncodingFilter</filter-name>

    <url-pattern>/*</url-pattern>

  </filter-mapping>

 

頁面靜態化

 

1 說明

你到“當當”搜索最多的是什么分類,沒錯,就是Java分類!你猜猜,你去搜索Java分類時,“當當”會不會去查詢數據庫呢?當然會了,不查詢數據庫怎么獲取Java分類下的圖書呢!其實每天都有很多人去搜索“Java分類”的圖書,每次都去訪問數據庫,這會有性能上的缺失!如果是在訪問靜態頁面(html)那么就會快的多了!靜態頁面本身就比動態頁面快很多倍,而且動態頁面總是要去數據庫查詢,這會更加降低速度!

頁面靜態化是把動態頁面生成的html保存到服務器的文件上,然后再有相同請求時,不再去執行動態頁面,而是直接給用戶響應上次已經生成的靜態頁面。而且靜態頁面還有助與搜索引擎找到你!

 

2 查看圖書分類

我們先來寫一個小例子,用來查看不同分類的圖書。然后我們再去思考如何讓動態頁面靜態化的問題。

index.jsp

  <body>

<a href="<c:url value='/BookServlet'/>">全部圖書</a><br/>

<a href="<c:url value='/BookServlet?category=1'/>">JavaSE分類</a><br/>

<a href="<c:url value='/BookServlet?category=2'/>">JavaEE分類</a><br/>

<a href="<c:url value='/BookServlet?category=3'/>">Java框架分類</a><br/>

  </body>

 

 

BookServlet.java

public class BookServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)

           throws ServletException, IOException {

       BookService bookService = new BookService();[崔74] 

       List<Book> bookList = null;

        String param = request.getParameter("category");[崔75] 

       if(param == null || param.isEmpty()) {

           bookList = bookService.findAll();[崔76] 

       } else {

           int category = Integer.parseInt(param);

           bookList = bookService.findByCategory(category);

[崔77]     }

      

       request.setAttribute("bookList", bookList);

       request.getRequestDispatcher("/show.jsp").forward(request, response);

[崔78]  }

}

 

show.jsp

<table border="1" align="center" width="50%">

    <tr>

       <th>圖書名稱</th>

       <th>圖書單價</th>

       <th>圖書分類</th>

    </tr>

   

  <c:forEach items="${bookList }" var="book">

    <tr>

       <td>${book.bname }</td>

       <td>${book.price }</td>

       <td>

           <c:choose>

              <c:when test="${book.category eq 1}"><p style="color:red;">JavaSE分類</p></c:when>

              <c:when test="${book.category eq 2}"><p style="color:blue;">JavaEE分類</p></c:when>

              <c:when test="${book.category eq 3}"><p style="color:green;">Java框架分類</p></c:when>

           </c:choose>

       </td>

    </tr>

  </c:forEach>

</table>

 

 

3 分析

  我們的目標是在用戶第一次訪問頁面時生成靜態頁面,然后讓請求重定向到靜態頁面上去。當用戶再次訪問時,直接重定向到靜態頁面上去。

我們需要為不同的請求生成靜態頁面,例如用戶訪問BookServlet?category=1時,我們要生成靜態頁面,當用戶訪問BookServlet?category=2時,也要生成靜態頁面。即不同的參數生成不同的靜態頁面!

我們可以使用category為key,靜態頁面的路徑為value,保存到一個Map中,然后再把Map保存到ServletContext中。沒有對應的靜態頁面時,我們生成靜態頁面,再重定向到靜態頁面,如果存在靜態頁面,那么直接重定向即可。

 

 

 

 

StaticResponse.java

public class StaticResponse extends HttpServletResponseWrapper {

    private PrintWriter pw;

 

    public StaticResponse(HttpServletResponse response, String filepath)

           throws FileNotFoundException, UnsupportedEncodingException {

       super(response);

       pw = new PrintWriter(filepath, "UTF-8");[崔79] 

    }

 

    public PrintWriter getWriter[崔80] () throws IOException {

       return pw;

    }

 

    public void close() throws IOException {

       pw.close();[崔81] 

    }

}

 

StaticFilter.java

public class StaticFilter implements Filter {

    private ServletContext sc;

   

    public void destroy() {

    }

 

    public void doFilter(ServletRequest request, ServletResponse response,

           FilterChain chain) throws IOException, ServletException {

       HttpServletRequest req = (HttpServletRequest) request;

       HttpServletResponse res = (HttpServletResponse) response;

 

       String key = "key_" + request.getParameter("category");[崔82] 

             

       Map<String,String> map = (Map<String, String>) sc.getAttribute("pages");[崔83] 

       if(map == null) {

           map = new HashMap<String,String>();

           sc.setAttribute("pages", map);

       }[崔84] 

      

       if(map.containsKey(key)) {

           res.sendRedirect(req.getContextPath() + "/staticPages/" + map.get(key));

           return;

       }[崔85] 

 

       String html = key + ".html";[崔86] 

       String realPath = sc.getRealPath("/staticPages/" + html);[崔87] 

       StaticResponse sr = new StaticResponse(res, realPath);[崔88] 

       chain.doFilter(request, sr);[崔89] 

       sr.close();[崔90] 

 

       res.sendRedirect(req.getContextPath() + "/staticPages/" + html);[崔91] 

       map.put(key, html);[崔92] 

    }

 

    public void init(FilterConfig fConfig) throws ServletException {

       this.sc = fConfig.getServletContext();

    }

}

 

 

 


 [崔1]不去理會它

 [崔2]當訪問被攔截資源時,doFilter()方法會被調用!我們先不去管它的參數是什么作用!

 [崔3]不去理會它

 [崔4]指定要攔截的路徑!當用戶訪問index.jsp頁面時,HelloFilter就會被執行

 [崔5]執行index.jsp之前執行

 [崔6]放行!表示執行index.jsp

 [崔7]在執行index.jsp后執行這一句

 [崔8]因為MyFilter1配置在前面,所以先執行MyFilter1的doFilter()方法。

 [崔9]b.jsp為目標資源,當直接請求b.jsp時,會執行過濾器

 [崔10]當轉發到b.jsp頁面時,會執行過濾器

 [崔11]當沒有給出攔截方式時,那么默認為REQUEST

 [崔12]當轉發到b.jsp頁面時,會執行過濾器!因為已經給出了<dispatcher>FORWARD</dispatcher>了,那么就沒有默認的REQUEST了!所以只有在轉發到b.jsp時才會執行過濾,而轉發到b.jsp時,不會執行b.jsp

 [崔13]攔截方式為ERROR

 [崔14]把b.jsp執行為500的錯誤頁面

 [崔15]當用戶訪問a.jsp頁面時會拋出異常,即500了!

這時服務器會轉發到b.jsp,在這之前會執行過濾器!

 [c16]幾乎是的Sevlet中都需要寫request.setCharacterEndoing() 可以把它入到一個Filter中

 [c17]回程攔截!

 [崔18]表示過濾所有資源

 [崔19]這里沒有指定<url-pattern>,而是指定<servlet-name>!注意,它與某個Servlet的配置名稱相同!

 [崔20]循環遍歷在ServletContext中的map,其中key是ip地址,value是訪問次數

 [崔21]保存ServletContext

 [崔22]創建一個Map,保存到ServletContext中

 [崔23]獲取請求方的ip

 [崔24]在context中獲取Map

 [崔25]在Map中獲取當前ip的訪問次數

 [崔26]如果這個ip在map中不存在,那么設置訪問次數為1

 [崔27]否則在原有次數上加1

 [崔28]把ip和次數設置到map中

 [崔29]把map存放到context中

 [崔30]放行!

 [崔31]用戶等級

 [崔32]所有用戶

 [崔33]在Map中保存兩個用戶,zhangSan的等級為1,liSi的等級為2

 [崔34]登錄方法

 [崔35]通過用戶名獲取用戶

 [崔36]如果用戶名不存在,返回null

 [崔37]如果密碼不對返回null,如果密碼正確返回用戶

 [崔38]當登錄出錯時返回到login.jsp頁面,顯示“用戶名或密碼錯誤”

 [崔39]獲取表單數據

 [崔40]調用userService的login()方法完成登錄

 [崔41]返回的user為null表示登錄失敗

 [崔42]在request 中保存錯誤信息,轉發到login.jsp頁面顯示錯誤信息

 [崔43]如果登錄成功,把user對象保存到session中,並轉發到index.jsp頁面

 [崔44]通過/user下的頁面

 [崔45]在session中獲取當前user對象

 [崔46]如果session中不存在user,說明當前用戶還沒有登錄

 [崔47]各客戶端瀏覽器打印錯誤消息

 [崔48]一定要返回,不然會向下執行“放行”的。

 [崔49]如果在session中存在user,那么就放行

 [崔50]過濾/admin目錄下的頁面

 [崔51]獲取session中的user

 [崔52]如果user為null,說明用戶沒有登錄

 [崔53]如果用戶等級小於2,說明是普通用戶,而不是管理員用戶

 [崔54]放行

 [崔55]獲取配置文件中的初始化參數:charset

 [崔56]如果沒有給Filter配置charset參數,那么設置編碼為UTF-8

 [崔57]處理POST請求編碼

 [崔58]處理響應編碼

 [崔59]包含HttpServletRequst

 [崔60]創建本類對象時,需要提供底層request,以及字符集

 [崔61]重寫getParameter()方法

 [崔62]把底層對象轉換成HttpServletRequest

 [崔63]獲取請求方法

 [崔64]如果是post請求

 [崔65]設置編碼,OK!

 [崔66]如果是GET請求

 [崔67]通過底層對象獲取參數

 [崔68]把參數轉碼

 [崔69]返回轉碼后的參數值

 [崔70]獲取初始化參數

 [崔71]如果沒有配置初始化參數,那么把字符集設置為utf-8

 [崔72]創建EncodingRequest對象,使用request為底層對象

 [崔73]放行!這時用戶獲取到的request就是EncodingRequest對象了!

 [崔74]創建BookService

 [崔75]獲取鏈接參數

 [崔76]如果沒有指定category,表示查詢所有圖書

 [崔77]如果指定了category,表示查詢指定分類的圖書

 [崔78]保存到request中,轉發到show.jsp頁面

 [崔79]使用路徑創建流!當使用該流寫數據時,數據會寫入到指定路徑的文件中。

 [崔80]jsp頁面會調用本方法獲取這個流!使用它來寫入頁面中的數據。這些數據都寫入到指定路徑的頁面中去了,即寫入到靜態頁面中。

 [崔81]刷新流,使緩沖區中的數據也寫入到目標!

 [崔82]獲取分類參數,分類參數可能是:1,2,3,null。使用分類參數做key

 [崔83]在ServletContext中獲取Map,首次訪問這個Map不存在。

 [崔84]如果Map不存在,那么創建一個Map,並存放到ServletContext中,這樣下次訪問Map就存在了。

 [崔85]查看Map中是否存在這個key,如果存在,那么獲取值,值就是這個參數對應的靜態頁面路徑。然后直接重定向到靜態頁面!

 [崔86]如果當前請求參數對應的靜態頁面不存在,那么就生成靜態頁面,首先靜態頁面的名稱為key,容顏名為html

 [崔87]生成真實路徑,下面會使用這個路徑創建靜態頁面。

 [崔88]創建自定義的response對象,傳遞待生成的靜態頁面路徑

 [崔89]放行

 [崔90]這個方法的作用是刷新緩沖區!

 [崔91]這時靜態頁面已經生成,重定向到靜態頁面

 [崔92]把靜態頁面保存到map中,下次訪問時,直接從map中獲取靜態頁面,不用再次生成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM