JavaWeb:过滤器Filter
1. 過濾器概述
1.1 什么是過濾器
過濾器會在一組資源(jsp、servlet、.css、.html等等)的前面執行!它可以讓請求得到目標資源,也可以不讓請求達到!過濾器有攔截請求的能力!
過濾器JavaWeb三大組件之一,它與Servlet很相似!不它過濾器是用來攔截請求的,而不是處理請求的
當用戶請求某個Servlet時,會先執行部署在這個請求上的Filter,如果Filter“放行”,那么會繼承執行用戶請求的Servlet;如果Filter不“放行”,那么就不會執行用戶請求的Servlet
其實可以這樣理解,當用戶請求某個Servlet時,Tomcat會去執行注冊在這個請求上的Filter,然后是否“放行”由Filter來決定。可以理解為,Filter來決定是否調用Servlet!當執行完成Servlet的代碼后,還會執行Filter后面的代碼
1.2 過濾器之hello world
其實過濾器與Servlet很相似,我們回憶一下如果寫的第一個Servlet應用!寫一個類,實現Servlet接口!沒錯,寫過濾器就是寫一個類,實現Filter接口
- void init(FilterConfig):創建之后,馬上執行;Filter會在服務器啟動時就創建!
- void destory():銷毀之前執行!在服務器關閉時銷毀
- void doFilter(ServletRequest,ServletResponse,FilterChain):每次過濾時都會執行
第二步也與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>應該沒有問題吧,都可以看懂吧!
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...");chain.doFilter(request, response);System.out.println("filter end..."); }有很多同學總是錯誤的認為,一個請求在給客戶端輸出之后就算是結束了,這是不對的!其實很多事情都需要在給客戶端響應之后才能完成!
2. 過濾器詳細
2.1 過濾器的生命周期
我們已經學習過Servlet的生命周期,那么Filter的生命周期也就沒有什么難度了!
- init(FilterConfig)
在服務器啟動時會創建Filter實例,并且每個類型的Filter只創建一個實例,從此不再創建!在創建完Filter實例后,會馬上調用init()方法完成初始化工作,這個方法只會被執行一次
- doFilter(ServletRequest req,ServletResponse res,FilterChain chain)
這個方法會在用戶每次訪問“目標資源(<url-pattern>index.jsp</url-pattern>)”時執行,如果需要“放行”,那么需要調用FilterChain的doFilter(ServletRequest,ServletResponse)方法,如果不調用FilterChain的doFilter()方法,那么目標資源將無法執行
- destroy()
服務器會在創建Filter對象之后,把Filter放到緩存中一直使用,通常不會銷毀它。一般會在服務器關閉時銷毀Filter對象,在銷毀Filter對象之前,服務器會調用Filter對象的destory()方法。
2.2 FilterConfig
你已經看到了吧,Filter接口中的init()方法的參數類型為FilterConfig類型。它的功能與ServletConfig相似,與web.xml文件中的配置信息對應。下面是FilterConfig的功能介紹:
| ServletContext | getServletContext() | 獲取ServletContext的方法 |
| String | getFilterName() | 獲取Filter的配置名稱;與<filter-name>元素對應 |
| String | getInitParameter(String name) | 獲取Filter的初始化配置,與<init-param>元素對應 |
| Enumeration | getInitParameterNames() | 獲取所有初始化參數的名稱 |
2.3 FilterChain
doFilter()方法的參數中有一個類型為FilterChain的參數,它只有一個方法
doFilter(ServletRequest,ServletResponse);//放行前面我們說doFilter()方法的放行,讓請求流訪問目標資源!但這么說不嚴密,其實調用該方法的意思是,“我(當前Filter)”放行了,但不代表其他人(其他過濾器)也放行。
也就是說,一個目標資源上,可能部署了多個過濾器,就好比在你去北京的路上有多個打劫的匪人(過濾器),而其中第一伙匪人放行了,但不代表第二伙匪人也放行了,所以調用FilterChain類的doFilter()方法表示的是執行下一個過濾器的doFilter()方法,或者是執行目標資源!
如果當前過濾器是最后一個過濾器,那么調用chain.doFilter()方法表示執行目標資源,而不是最后一個過濾器,那么chain.doFilter()表示執行下一個過濾器的doFilter()方法。
2.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...2.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
| REQUEST | 直接訪問目標資源時執行過濾器。包括:在地址欄中直接訪問、表單提交、超鏈接、重定向,只要在地址欄中可以看到目標資源的路徑,就是REQUEST |
| FORWARD | 轉發訪問執行過濾器。包括RequestDispatcher#forward()方法、<jsp:forward>標簽都是轉發訪問 |
| INCLUDE | 包含訪問執行過濾器。包括RequestDispatcher#include()方法、<jsp:include>標簽都是包含訪問 |
| ERROR | 當目標資源在web.xml中配置為<error-page>中時,并且真的出現了異常,轉發到目標資源時,會執行過濾器 |
可以在<filter-mapping>中添加0~n個<dispatcher>子元素,來說明當前訪問的攔截方式
其實最為常用的就是REQUEST和FORWARD兩種攔截方式,而INCLUDE和ERROR都比較少用!其中INCLUDE比較好理解,我們這里不再給出代碼,學員可以通過FORWARD方式修改,來自己測試。而ERROR方式不易理解,下面給出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>2.6 過濾器的應用場景
過濾器的應用場景:
- 執行目標資源之前做預處理工作,例如設置編碼,這種試通常都會放行,只是在目標資源執行之前做一些準備工作
- 通過條件判斷是否放行,例如校驗當前用戶是否已經登錄,或者用戶IP是否已經被禁用
- 在目標資源執行后,做一些后續的特殊處理工作,例如把目標資源輸出的數據進行處理
2.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,這時會執行過濾器。
2.8 Filter小結
Filter的三個方法:
- void init(FilterConfig):在Tomcat啟動時被調用
- void destroy():在Tomcat關閉時被調用
- void doFilter(ServletRequest,ServletResponse,FilterChain):每次有請求時都調用該方法
FilterConfig類:與ServletConfig相似,用來獲取Filter的初始化參數
- 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()方法之后的語句會在目標資源執行之后執行
四各攔截方式:REQUEST、FORWARD、INCLUDE、ERROR,默認是REQUEST方式
- REQUEST:攔截直接請求方式
- FORWARD:攔截請求轉發方式
- INCLUDE:攔截請求包含方式
- ERROR:攔截錯誤轉發方式
3. 分ip統計網站的訪問次數
| 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中的數據
3.1 說明
網站統計每個IP地址訪問本網站的次數。
3.2 分析
因為一個網站可能有多個頁面,無論哪個頁面被訪問,都要統計訪問次數,所以使用過濾器最為方便。
因為需要分IP統計,所以可以在過濾器中創建一個Map,使用IP為key,訪問次數為value。當有用戶訪問時,獲取請求的IP,如果IP在Map中存在,說明以前訪問過,那么在訪問次數上加1,即可;IP在Map中不存在,那么設置次數為1。
把這個Map存放到ServletContext中!
3.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>4. 粗粒度權限控制
攔截是否登錄、攔截用戶名admin權限
RBAC -> 基于角色的權限控制
- tb_user
- tb_role
- tb_userrole
- tb_menu(增、刪、改、查)
- tb_rolemenu
4.1、說明
我們給出三個頁面:index.jsp、user.jsp、admin.jsp。
- index.jsp:誰都可以訪問,沒有限制
- user.jsp:只有登錄用戶才能訪問
- admin.jsp:只有管理員才能訪問
4.2、分析
設計User類:username、password、grade,其中grade表示用戶等級,1表示普通用戶,2表示管理員用戶。
當用戶登錄成功后,把user保存到session中。
創建LoginFilter,它有兩種過濾方式:
- 如果訪問的是user.jsp,查看session中是否存在user;
- 如果訪問的是admin.jsp,查看session中是否存在user,并且user的grade等于2。
4.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);} }4.4 禁用資源緩存
瀏覽器只是要緩存頁面,這對我們在開發時測試很不方便,所以我們可以過濾所有資源,然后添加去除所有緩存!
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);} }但是要注意,有的瀏覽器可能不會理會你的設置,還是會緩存的!這時就要在頁面中使用時間戳來處理了。
5. 解決全站字符亂碼
POST和GET中文編碼問題
servlet:
//POST request.setCharacterEncoding(“utf-8”); //GET String username = request.getParameter(“username”); username = new String(username.getBytes(“ISO-8859-1”), “utf-8”);5.1、說明
亂碼問題:
- 獲取請求參數中的亂碼問題
- 響應的亂碼問題:
基本上在每個Servlet中都要處理亂碼問題,所以應該把這個工作放到過濾器中來完成。
5.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.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對象進行增強的手段:
繼承:AA類繼承a對象的類型:A類,然后重寫fun1()方法,其中重寫的fun1()方法就是被增強的方法。但是,繼承必須要知道a對象的真實類型,然后才能去繼承。如果我們不知道a對象的確切類型,而只知道a對象是IA接口的實現類對象,那么就無法使用繼承來增強a對象了
裝飾者模式:AA類去實現a對象相同的接口:IA接口,還需要給AA類傳遞a對象,然后在AA類中所有的方法實現都是通過代理a對象的相同方法完成的,只有fun1()方法在代理a對象相同方法的前后添加了一些內容,這就是對fun1()方法進行了增強
動態代理:動態代理與裝飾者模式比較相似,而且是通過反射來完成的。動態代理會在最后一天的基礎加強中講解,這里就不再廢話了。
對request對象進行增強的條件,剛好符合裝飾者模式的特點!因為我們不知道request對象的具體類型,但我們知道request是HttpServletRequest接口的實現類。這說明我們寫一個類EncodingRequest,去實現HttpServletRequest接口,然后再把原來的request傳遞給EncodingRequest類!在EncodingRequest中對HttpServletRequest接口中的所有方法的實現都是通過代理原來的request對象來完成的,只有對getParameter()方法添加了增強代碼!
JavaEE已經給我們提供了一個HttpServletRequestWrapper類,它就是HttpServletRequest的包裝類,但它做任何的增強!你可能會說,寫一個裝飾類,但不做增強,其目的是什么呢?使用這個裝飾類的對象,和使用原有的request有什么分別呢?
HttpServletRequestWrapper類雖然是HttpServletRequest的裝飾類,但它不是用來直接使用的,而是用來讓我們去繼承的!當我們想寫一個裝飾類時,還要對所有不需要增強的方法做一次實現是很心煩的事情,但如果你去繼承HttpServletRequestWrapper類,那么就只需要重寫需要增強的方法即可了。
5.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>6. 頁面靜態化
6.1 說明
你到“當當”搜索最多的是什么分類,沒錯,就是Java分類!你猜猜,你去搜索Java分類時,“當當”會不會去查詢數據庫呢?當然會了,不查詢數據庫怎么獲取Java分類下的圖書呢!其實每天都有很多人去搜索“Java分類”的圖書,每次都去訪問數據庫,這會有性能上的缺失!如果是在訪問靜態頁面(html)那么就會快的多了!靜態頁面本身就比動態頁面快很多倍,而且動態頁面總是要去數據庫查詢,這會更加降低速度!
頁面靜態化是把動態頁面生成的html保存到服務器的文件上,然后再有相同請求時,不再去執行動態頁面,而是直接給用戶響應上次已經生成的靜態頁面。而且靜態頁面還有助與搜索引擎找到你!
6.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>6.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");}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();} }總結
以上是生活随笔為你收集整理的JavaWeb:过滤器Filter的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记住linux terminal下的快捷
- 下一篇: JavaWeb开发概述