Javaweb學習筆記——(二十一)——————過濾器
過濾器 過濾器概述 1.什麼是過濾器: 過濾器javaweb三大元件之一,它與Serlvet很相似,不過它過濾器是用來攔截請求的,而不是處理 請求的。 當用戶請求某個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 (FilterConfig filterConfig) throws ServletException {} public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Hello Filter"); } public void destroy () {} }
第二步也與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 </url-pattern> </filter-mapping>
當用戶訪問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..."); chain.doFilter(request, response); System.out.println("filter end..."); }
錯誤的認為,一個請求在給客戶端輸出之後就算是結束了,這是不對的!其實很多事情都需要在給客戶端響應之後才能完成!
過濾器詳細 1.過濾器的生命週期 學習過Servlet的生命週期,那麼Filter的生命週期也是類似 *init(FilterConfig):在伺服器啟動時會建立Filter例項,並且每個型別的Filter只建立一個例項, 從此不再建立。在建立完Filter例項後,會馬上呼叫init()方法完成初始化工作,這個方法只會 被執行一次; *doFilter(ServletRequest request, ServletResponse response, FilterChain chain):這個方法會在使用者 沒次訪問“目標資源(<url-pattern>index.jsp</url-pattern>)”時執行,如果需要“放行”,那麼需要呼叫FilterChain的 doFilter(ServletRequest, ServletResponse)方法,如果不呼叫FilterChain的doFilter()方法,那麼目標資源將無法執行; *destroy()伺服器會在建立Filter物件之後,把Filter放到快取中一直使用,通常不會銷燬 它。一般會在伺服器關閉時銷燬Filter物件,在銷燬Filter物件之前,伺服器會呼叫Filter 物件的destory()方法。
2.FilterConfig Filter介面中的init()方法的引數型別為FilterConfig型別。它的功能與ServletConfig相似,與web.xml檔案中的配置資訊對應。 下面是FilterConfig的功能介紹: *ServletContext getServletContext():獲取ServeltContext的方法; *String getFilterName():獲取Filter的配置名稱,與<filter-name>元素對應; *String getInitParameter(String name):獲取Fitler的初始化配置,與<init-param>元素對應; *Enumeration getInitParameterNames():獲取所有初始化引數的名稱。
3.FilterChain doFilter()方法的引數中有一個型別為FilterChain的引數,它只有一個方法: doFilter(ServletRequest, ServletResponse). 前面說到doFilter()方法的放行,讓請求流訪問目標資源。但這麼說不嚴密,其實呼叫該方法 的意思是,“我(當前Fitler)”放行了,但不代表其他人(其他過濾器)也放行。 也就是說,一個目標資源上,可能部署了多個過濾器,就好比你去北京的路上有多個打劫的 土匪(過濾器),而其中第一夥土匪放行了,但不代表第二夥土匪也放行了,所以呼叫FilterChain類的 doFilter()方法表示的是執行了下一個過濾器的doFilter()方法,或者執行目標資源。 如果當前過濾器是最後一個過濾器,那麼呼叫chain.doFilter()方法表示執行目標資源,而不是最後一個 過濾器,那麼chain.doFilter()表示執行下一個過濾器的doFilter()方法。
4.多個過濾器執行順序 一個目標資源可以指定多個過濾器,過濾器的執行順序是在web.xml檔案中的部署順序: <filter> <filter-name>myFilter1 </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.getRequestDsipathcer("/b.jsp").forward(request, reponse);時,就不會在執行過濾器,也就是說, 在預設情況下,只能直接訪問目標資源才會執行過濾器,而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/filtertext/b.jsp--->直接訪問b.jsp時,會執行過濾器內容; http://localhost:8080/filtertest/a.jsp--->訪問a.jsp,但是a.jsp會forward到b.jsp,這時就不會執行過濾器。
其實過濾器有四種攔截方式。分別是:request,forward、include、error *request:直接訪問目標資源時執行過濾器。包括:在位址列中直接訪問、表單提交、超連結、重定向,只要在位址列中 可以看到目標資源的路徑,就是request; *forward:轉發訪問執行過濾器。包括RequsetDispatcher#forward()方法、<jsp:forward>標籤都是轉發訪問; *include:包含訪問執行過濾器。包括RequestDisepatcher#forward()方法、<jsp:include>標籤都是包含訪問; *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> <dispatcher>FORWARD</dispatcher> </filter-mapping> <filter-mapping> <filter-name>myfilter</filter-name> <url-pattern>/b.jsp</url-pattern> </filter-mapping>
<filter-mapping> <filter-name>myfilter</filter-name> <url-pattern>/b.jsp</url-pattern> <dispatcher>FORWARD</dispatcher> </filter-mapping>
其實最為常用的就是request和forward兩種攔截方式,而include和error都比較少用,其中include比較好理解, 這就不再給出程式碼。下面給出error攔截方式例子 <filter-mapping> <filter-name>myfilter</filter-name> <url-pattern>/b.jsp</url-pattern> <dispatcher>ERROR</dispatcher> </filter-mapping> <error-page> <error-code>500</error-code> <location>/b.jsp</location> </error-page> <body> <h1>a.jsp</h1> <% if(true) throw new RuntimeException("嘻嘻~"); %> </body>
6.過濾器的應用場景 過濾器的應用場景 *執行目標資源之前做預處理工作,例如設定編碼,這種通常使用放行。只是在目標資源執行之前做一些準備工作; *通過條件判斷是否放行,例如校驗當前使用者是否已經登入,或者使用者ip是否已經被禁用; *在目標資源執行後,做一些後續的特殊處理工作,例如把目標資源輸出的資料進行處理;
7.設定目標資源 在web.xml檔案中不是Filter時,可以通過“*”來執行目標資源: <filter-mapping> <filter-name>myfilter</filter-name> <url-pattern>/*</url-pattern> </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> </filter-mapping> 當用戶訪問http://localhost:8080/filtertest/abc時,會執行名字為myservlet的Servlet,這時會執行過濾器。
8.Fitler小結 Filter的三個方法: *void init():在Tomcat啟動時被呼叫; *void destroy():在Tomcat關閉時被呼叫; *void doFilter(ServletRequest, ServletResponse, FilterChain):每次請求時都呼叫該方法;
FitlerConfig類:與ServletConfig相似,用來獲取Fitler的初始化引數 *ServletContext getServletContext():獲取ServletContext的方法; *String getFilterName():獲取Filter的配置名稱; *String getInitParameter(String name):獲取Filter的初始化配置,與<init-param>元素對應; *Enumeration getInitParameterNames():獲取所有初始化引數的名稱。
FilterChain類: *void doFilter(ServletRequest, ServletResponse):放行,表示執行下一個過濾器,或者執行目標資源。可以在呼叫FilterChain的 doFilter()方法的前後新增語句,在FilterChain的doFilter()方法之前的語句會在目標資源執行之前執行,在FilterChain的doFilter()方法 之後的語句會在目標資源執行之後執行。
四種攔截方式:Requset,forward、include、error,預設是request方式。 *request:攔截直接請求方式 *forward:攔截請求轉發方式 *include:攔截請求包含方式 *error:攔截錯誤轉發方式
過濾器應用案例 分ip統計網站訪問次數 ip count 192.168.1.111 2 192.168.2.112 89
統計工作需要在所有資源之前都執行,那麼就可以放到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> </table> </body> IPFilter public class IPFilter implements Filter { private ServletContext context;
public void init(FilterConfig fConfig) throws ServletException { context = fConfig.getServletContext(); Map<String, Integer> ipCountMap = Collections .synchronizedMap(new LinkedHashMap<String, Integer>()); 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();
Map<String, Integer> ipCountMap = (Map<String, Integer>) context .getAttribute("ipCountMap");
Integer count = ipCountMap.get(ip); if (count == null) { count = 1; } else { count += 1; } ipCountMap.put(ip, count);
context.setAttribute("ipCountMap", ipCountMap); chain.doFilter(request, response); }
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->基於角色的許可權控制 *tb_user *tb_role *tb_userrole *tb_menu(增、刪、改、查) *tb_rolemenu
1.說明 我們給出三個頁面:index.jsp、user.jsp、admin.jsp。 *index.jsp:誰都可以訪問,沒用限制; *user.jsp:只用登入使用者才能訪問; *admin.jsp:只有管理員才能訪問。
2.分析: 設計Uesr類:username、password、prade,其中grade表示使用者等級,1表示普通等級,2表示管理員使用者。 當用戶登入成功後,把user儲存到session中。 建立LoginFilter,它有兩種過濾方式: *如果訪問的是user.jsp,檢視session中是否存在user; *如果訪問的是admin.jsp,檢視session中是否存在user,並且user的grade等於2。
3.程式碼 User.java public class User { private String username; private String password; private int grade; … }
為了方便,這裡就不適用資料庫,所以我們需要在UserService中建立一個Map,用來儲存所有使用者。Map中的key中使用者名稱, value為User物件。 UserService.java public class UserService { private static Map<String,User> users = new HashMap<String,User>(); static { users.put("zhangSan", new User("zhangSan", "123", 1)); users.put("liSi", new User("liSi", "123", 2)); } public User login(String username, String password) { User user = users.get(username); if(user == null) return null; return user.getPassword().equals(password) ? user : null; } }
login.jsp <body> <h1>登入</h1> <p style="font-weight: 900; color: red">${msg }</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"); UserService userService = new UserService(); User user = userService.login(username, password); if(user == null) { request.setAttribute("msg", "使用者名稱或密碼錯誤"); request.getRequestDispatcher("/login.jsp").forward(request, response); } else { request.getSession().setAttribute("user", user); request.getRequestDispatcher("/index.jsp").forward(request, response); } } }
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/*</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"); if(user == null) { response.getWriter().print("您還沒有登入"); return; } chain.doFilter(request, response); } }
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/*</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"); if(user == null) { response.getWriter().print("您還沒有登入!"); return; } if(user.getGrade() < 2) { response.getWriter().print("您的等級不夠!"); return; } chain.doFilter(request, response); } }
禁用資源快取 瀏覽器只是要快取頁面,這對我們在開發時測試很不方便,所以我們可以過濾所有資源,然後新增去除所有快取 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中文編碼問題)
Serlvet: *POST:requset.setCharacterEncodin("utf-8"); *GET: *String username = requset。geteParameter("username"); 1.說明: 亂碼問題: *獲取請求引數中的亂碼問題 **POST請求:request。setCharacterEncoding("utf-8"); **GET請求:new String(request.getParameter("xxx").getBytes("iso-8859-1"), "utf-8");
*響應的亂碼問題: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"); if(charset == null || charset.isEmpty()) { charset = "UTF-8"; } request.setCharacterEncoding(charset); response.setContentType("text/html;charset=" + charset); chain.doFilter(request, response); } }
如果是POST請求,當執行目標Servlet時,Servlet中呼叫request.getParameter()方法時,就會根據request.serCharacterEncoding() 設定的編碼來轉碼,這說明在過濾器中呼叫request.setCharacterEncoding()方法會影響在目標Servlet中的request.getParameter()方法的 行為。 但是如果是GET請求,將如何影響request.getParameter()方法的行為呢?這將不好實現,不可能先呼叫request.getParameter()方法獲取引數, 然後手動轉碼後,再施加在request中,因為request只有getParameter(),而沒有setParameter()方法。
處理GET請求引數編碼問,需要在Filtter中放行時,把request物件給“調包”了,也就是讓目標Servlet使用我們“調包”之後的request物件。 這說明我們需要保證“調包”之後的request物件中所有方法都有與“調包”之前一樣可以使用,並且getParameter()方法還要有能力返回轉碼後的引數。
這可能讓你想起了“繼承”,但是這裡不能用繼承,而是“裝飾者(Decorator Pattern)”。 下面三種對a物件機械能增強的手段: *繼承:AA類繼承a物件的型別:A類,然後去重寫fun1()方法,其中重寫的fun1()方法就是被增強的方法。但是,繼承必須要知道a物件的真實型別, 然後才能去繼承。如果我們不知道a物件的確切型別,而只知道a物件的IA介面的實現類物件,那麼就無法使用繼承來增強a物件了;
*裝飾者模式:AA類去實現a物件相同的介面:IA介面,還需要給AA類傳遞a物件,然後在AA類中所有的方法實現都是 通過代理a物件的相同方法完成的,只有fun1()方法在代理a物件相同方法的前後1新增一些內容,這就是對fun1()方法進行了增強。
*動態代理:動態代理與裝飾者模式比較類似,而且是通過反射來完成的。
對request物件進行增強的條件,剛好符合裝飾者模式的特點。因為我們不知道request物件的具體型別,但是我們知道request是HttpServletRequest介面的實現類。 這說明我們寫一個類EncodingRequest,去實現HttpServletRequest介面,然後再把原來的request傳遞給EncodingRequest類。在EncodingRequest中對 HttpServletRequest介面中的所有方法的實現都是通過代理原來的request物件來完成的,只有對getParameter()方法添加了增強程式碼。 javaEE已經給提供了一個HttpServletRequest的裝飾類,但是它不是用來直接使用的,而是用來讓我們去繼承的,它就是HttpServletRequest類,但它不做任何的增強, 在這裡,一個裝飾類,不做任何增強,有什麼意義啊?使用這個裝飾類的物件,和使用原有的request有什麼分別呢? HttpServletRequestWrapper類雖然是HttpServletRequest的裝飾類,但它不是用來直接使用的,而是用來讓我們去繼承的,當我們想寫一個裝飾類的時候, 還要對所有不需要增強的方法做一次實現的是很複雜的事情,但是如果你只是去繼承HttpServletRequestWrapper類,那麼只需要重寫需要增強的方法即可了。
3.程式碼 EncodingRequest public class EncodingRequest extends HttpServletRequestWrapper { private String charset; public EncodingRequest(HttpServletRequest request, String charset) { super(request); this.charset = charset; }
public String getParameter(String name) { HttpServletRequest request = (HttpServletRequest) getRequest(); String method = request.getMethod(); if(method.equalsIgnoreCase("post")) { try { request.setCharacterEncoding(charset); } catch (UnsupportedEncodingException e) {} } else if(method.equalsIgnoreCase("get")) { String value = request.getParameter(name); try { value = new String(name.getBytes("ISO-8859-1"), charset); } catch (UnsupportedEncodingException e) { } return value; } 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"); if(charset == null || charset.isEmpty()) { charset = "UTF-8"; } response.setCharacterEncoding(charset); response.setContentType("text/html;charset=" + charset); EncodingRequest res = new EncodingRequest(request, charset); chain.doFilter(res, response); } }
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(); List<Book> bookList = null; String param = request.getParameter("category"); if(param == null || param.isEmpty()) { bookList = bookService.findAll(); } else { int category = Integer.parseInt(param); bookList = bookService.findByCategory(category); } request.setAttribute("bookList", bookList); request.getRequestDispatcher("/show.jsp").forward(request, response); } }
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時,也要生成靜態頁面,即不同1引數生成不同的靜態頁面 我們可以使用category為key,靜態頁面的路徑為value,儲存到一個Map中,然再把Map儲存到SerlvetContext中。沒有對應的靜態頁面, 我們生成靜態頁面,再重定向到靜態頁面,如果存靜態頁面,那麼直接重定向即可。
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"); }
public PrintWriter getWriter() throws IOException { return pw; }
public void close() throws IOException { pw.close(); } }
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"); Map<String,String> map = (Map<String, String>) sc.getAttribute("pages"); if(map == null) { map = new HashMap<String,String>(); sc.setAttribute("pages", map); } if(map.containsKey(key)) { res.sendRedirect(req.getContextPath() + "/staticPages/" + map.get(key)); return; }
String html = key + ".html"; String realPath = sc.getRealPath("/staticPages/" + html); StaticResponse sr = new StaticResponse(res, realPath); chain.doFilter(request, sr); sr.close();
res.sendRedirect(req.getContextPath() + "/staticPages/" + html); map.put(key, html); }
public void init(FilterConfig fConfig) throws ServletException { this.sc = fConfig.getServletContext(); } }