Filter实现页面静态化
在实际开发中,有时为了提高程序性能、减轻数据库访问压力以及对搜索引擎的优化,可以使用Filter实现动态页面静态化。页面静态化就是先于用户获取资源或数据库数据进而通过静态化处理,生成静态页面,所有人都访问这一个静态页面,而静态化处理的页面的访问速度要比动态页面快的多,因此程序性能会有大大的提升。接下来通过一张图来简单描述页面静态化的过程,如图1所示。
图1 页面静态化
图1中,当客户端首次访问页面时,Filter会自定义response输出缓存HTML源码。当客户端第二次访问页面时,就会直接访问静态页面,这样避免访问数据库。
接下来通过一个显示图书分类的案例,分步骤讲解如何使用Filter实现页面静态化,具体如下:
(1)首先写一个完整的查询图书的功能,测试成功后,再通过编写过滤器将动态的图书信息页面静态化。在MySQL中创建一个数据库chapter04,在该数据库中创建数据表t_book并插入数据,其中,bname为图书名称、price为图书价格、category为图书分类,具体SQL语句如下:
CREATE DATABASE chapter04;
USE chapter04;
CREATE TABLE t_book(
bid CHAR(32) PRIMARY KEY,
bname VARCHAR(100),
price NUMERIC(10,2),
category INT
);
INSERT INTO t_book VALUES('b1', 'JavaSE_1', 10, 1);
INSERT INTO t_book VALUES('b2', 'JavaSE_2', 15, 1);
INSERT INTO t_book VALUES('b3', 'JavaSE_3', 20, 1);
INSERT INTO t_book VALUES('b4', 'JavaSE_4', 25, 1);
INSERT INTO t_book VALUES('b5', 'JavaEE_1', 30, 2);
INSERT INTO t_book VALUES('b6', 'JavaEE_2', 35, 2);
INSERT INTO t_book VALUES('b7', 'JavaEE_3', 40, 2);
INSERT INTO t_book VALUES('b8', 'Java_framework_1', 45, 3);
INSERT INTO t_book VALUES('b9', 'Java_framework_2', 50, 3);
查询t_book表中的数据,查询结果如下:
mysql> SELECT * FROM t_book;
+-----+------------------+-------+----------+
| bid | bname | price | category |
+-----+------------------+-------+----------+
| b1 | JavaSE_1 | 10.00 | 1 |
| b2 | JavaSE_2 | 15.00 | 1 |
| b3 | JavaSE_3 | 20.00 | 1 |
| b4 | JavaSE_4 | 25.00 | 1 |
| b5 | JavaEE_1 | 30.00 | 2 |
| b6 | JavaEE_2 | 35.00 | 2 |
| b7 | JavaEE_3 | 40.00 | 2 |
| b8 | Java_framework_1 | 45.00 | 3 |
| b9 | Java_framework_2 | 50.00 | 3 |
+-----+------------------+-------+----------+
9 rows in set (0.04 sec)
(2)将所需的连接数据库包导入到chapter04项目中的lib文件夹下,创建包cn.itcast.domain并在包中编写一个Book类,具体代码如例1所示。
例1 Book.java
1 package cn.itcast.domain;
2 public class Book {
3 private String bid;
4 private String bname;
5 private double price;
6 private int category;
7 public String getBid() {
8 return bid;
9 }
10 public void setBid(String bid) {
11 this.bid = bid;
12 }
13 public String getBname() {
14 return bname;
15 }
16 public void setBname(String bname) {
17 this.bname = bname;
18 }
19 public double getPrice() {
20 return price;
21 }
22 public void setPrice(double price) {
23 this.price = price;
24 }
25 public int getCategory() {
26 return category;
27 }
28 public void setCategory(int category) {
29 this.category = category;
30 }
31 @Override
32 public String toString() {
33 return "Book [bid=" + bid + ", bname=" + bname + ", price=" + price
34 + ", category=" + category + "]";
35 }
36 }
(3)创建包cn.itcast.dao并在包中编写BookDao类,在本案例中会涉及到两个查询方法,一个是查询所有图书,一个按分类查询图书,,具体代码如例2所示。
例2 BookDao.java
1 package cn.itcast.dao;
2 import java.sql.SQLException;
3 import java.util.List;
4 import org.apache.commons.dbutils.QueryRunner;
5 import org.apache.commons.dbutils.handlers.BeanListHandler;
6 import cn.itcast.domain.Book;
7 import cn.itcast.jdbc.utils.JDBCUtils;//这个JDBCUtils与chapter03中的一样
8 public class BookDao {
9 private QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
10 //查询所有
11 public List<Book> findAll() {
12 try {
13 String sql = "select * from t_book";
14 return qr.query(sql, new BeanListHandler<Book>(Book.class));
15 } catch (SQLException e) {
16 throw new RuntimeException(e);
17 }
18 }
19 // 按分类查询
20 public List<Book> findByCategory(int category) {
21 try {
22 String sql = "select * from t_book where category=?";
23 return qr.query(sql, new BeanListHandler<Book>
24 (Book.class), category);
25 } catch (SQLException e) {
26 throw new RuntimeException(e);
27 }
28 }
29 }
(4)在包cn.itcast.chapter04.serrvlet下创建一个BookServlet。该Servlet要获取category参数,如果这个参数存在,说明是按分类查询,如果不存在,表示查询所有。然后将查询出的数据存成List并保存到request中转发到页面,具体代码如例3所示。
例3 BookServlet.java
1 package cn.itcast.chapter04.servlet;
2 import java.io.IOException;
3 import java.util.List;
4 import javax.servlet.ServletException;
5 import javax.servlet.http.HttpServlet;
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpServletResponse;
8 import cn.itcast.dao.BookDao;
9 import cn.itcast.domain.Book;
10 public class BookServlet extends HttpServlet {
11 public void doGet(HttpServletRequest request, HttpServletResponse
12 response) throws ServletException, IOException {
13 String param = request.getParameter("category");
14 BookDao dao = new BookDao();
15 List<Book> bookList = null;
16 // 如果category参数不存在,表示查询所有
17 if(param == null || param.trim().isEmpty()) {
18 bookList = dao.findAll();
19 } else {
20 int category = Integer.parseInt(param); //把参数转换成int类型
21 // 按分类查询图书
22 bookList = dao.findByCategory(category);
23 }
24 // 把图书保存到request中
25 request.setAttribute("bookList", bookList);
26 request.getRequestDispatcher("/show.jsp").forward(request,
27 response);
28 }
29 }
(5)在WebContent根目录中,编写index_book.jsp页面,用于显示图书分类,具体如例4所示。
例4 index_book.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
2 <%
3 String path = request.getContextPath();
4 String basePath = request.getScheme()+"://"+request.getServerName()+
5 ":"+request.getServerPort()+path+"/";
6 %>
7
8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
9 <html>
10 <head>
11 <base href="<%=basePath%>">
12
13 <title>My JSP 'index_book.jsp' starting page</title>
14 <meta http-equiv="pragma" content="no-cache">
15 <meta http-equiv="cache-control" content="no-cache">
16 <meta http-equiv="expires" content="0">
17 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
18 <meta http-equiv="description" content="This is my page">
19 </head>
20 <body>
21 <a href="<%=request.getContextPath()%>/BookServlet">全部图书</a><br/>
22 <a href="<%=request.getContextPath()%>/BookServlet?category=1">
23 JavaSE分类</a><br/>
24 <a href="<%=request.getContextPath()%>/BookServlet?category=2">
25 JavaEE分类</a><br/>
26 <a href="<%=request.getContextPath()%>/BookServlet?category=3">
27 Java框架分类</a><br/>
28 </body>
29 </html>
(6)在WebContent根目录中,编写一个show.jsp页面,用于显示每类图书信息,具体代码如例5所示。
例5 show.jsp
1 <%@page import="cn.itcast.domain.Book"%>
2 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4 <html>
5 <head>
6 <title>My JSP 'show.jsp' starting page</title>
7 <meta http-equiv="pragma" content="no-cache">
8 <meta http-equiv="cache-control" content="no-cache">
9 <meta http-equiv="expires" content="0">
10 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
11 <meta http-equiv="description" content="This is my page">
12 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
13 <!--
14 <link rel="stylesheet" type="text/css" href="styles.css">
15 -->
16 </head>
17 <body>
18 <table border="1" align="center" width="50%">
19 <tr>
20 <th>图书名称</th>
21 <th>图书单价</th>
22 <th>图书分类</th>
23 </tr>
24 <%
25 List<Book> list = (List) request.getAttribute("bookList");
26 for (Book b : list) {
27 %>
28 <tr>
29 <td><%=b.getBname()%></td>
30 <td><%=b.getPrice()%></td>
31 <td>
32 <% if (b.getCategory() == 1) {%>
33 <p style="color: red;">JavaSE分类</p>
34 <%} else if (b.getCategory() == 2) {%>
35 <p style="color: blue;">JavaEE分类</p>
36 <%} else {%>
37 <p style="color: green;">Java框架分类</p>
38 <%}%>
39 </td>
40 </tr>
41 <%}%>
42 </table>
43 </body>
44 </html>
页面完成后,先验证查看图书分类的功能是否完成,重新启动Web服务器,打开IE浏览器,在地址栏中输入http://localhost:8080/chapter04/index_book.jsp
,查询结果如图2。
图2 查询结果
点击查看JavaSE分类,查询结果如图3。
图3 查询结果
从图3中的URL地址可以看出,本次查询是通过BookServlet查询数据库所得结果。接下来,将编写过滤器来实现页面静态化。
(7)在包cn.itcast.chapter04.filter下编写StaticResponse类,该类为自定义的Response,重写了Writer()方法,使其向指定的文件输出。具体代码如例6所示。
例6 StaticResponse.java
1 package cn.itcast.chapter04.filter;
2 import java.io.FileNotFoundException;
3 import java.io.IOException;
4 import java.io.PrintWriter;
5 import java.io.UnsupportedEncodingException;
6 import javax.servlet.http.HttpServletResponse;
7 import javax.servlet.http.HttpServletResponseWrapper;
8 public class StaticResponse extends HttpServletResponseWrapper {
9 private HttpServletResponse response;
10 private PrintWriter pw;
11 // 其中staticPath为静态页面的路径
12 public StaticResponse(HttpServletResponse response, String staticPath)
13 throws FileNotFoundException, UnsupportedEncodingException {
14 super(response);
15 this.response = response;
16 // pw与指定文件绑定在一起,当使用pw输出时,就是向指定的文件输出
17 pw = new PrintWriter(staticPath, "utf-8");
18 }
19 // 当show.jsp输出页面中的内容时,使用的就是getWriter()获取的流对象。
20 public PrintWriter getWriter() throws IOException {
21 return pw;
22 }
23 // 关闭方法在过滤器中调用,可以刷新缓冲区。
24 public void close() {
25 pw.close();
26 }
27 }
(8)在cn.itcast.chapter04.filter包下编写过滤器StaticFilter类,该类实现了判断是否存在静态页面,如果存在静态页面,则重定向到静态页面,如果静态页面不存在,那么生成静态页面,其具体代码如例7所示。
例7 StaticFilter.java
1 package cn.itcast.chapter04.filter;
2 import java.io.IOException;
3 import java.util.HashMap;
4 import java.util.Map;
5 import javax.servlet.Filter;
6 import javax.servlet.FilterChain;
7 import javax.servlet.FilterConfig;
8 import javax.servlet.ServletContext;
9 import javax.servlet.ServletException;
10 import javax.servlet.ServletRequest;
11 import javax.servlet.ServletResponse;
12 import javax.servlet.http.HttpServletRequest;
13 import javax.servlet.http.HttpServletResponse;
14 public class StaticFilter implements Filter {
15 private FilterConfig config;
16 public void destroy() {
17 }
18 public void doFilter(ServletRequest request, ServletResponse response,
19 FilterChain chain) throws IOException, ServletException {
20 // 把request和response都强转成http的
21 HttpServletRequest req = (HttpServletRequest) request;
22 HttpServletResponse res = (HttpServletResponse) response;
23 // 1. 获取Map
24 // 获取ServletContext
25 ServletContext sc = config.getServletContext();
26 Map<String, String> staticMap = (Map<String, String>) sc
27 .getAttribute("static_map");
28 if (staticMap == null) {
29 staticMap = new HashMap<String, String>();
30 sc.setAttribute("static_map", staticMap);
31 }
32 // 2. 通过访问路径获取对应的静态页面
33 // 生成key:book_前缀,后缀为category的值
34 String category = request.getParameter("category");
35 // 可能有:book_null、book_1、book_2、book_3
36 String key = "book_" + category;
37 // 查看这个路径对应的静态页面是否存在
38 if (staticMap.containsKey(key)) {// 如果静态页面已经存在
39 String staticPath = staticMap.get(key);// 获取静态页面路径
40 // 重定向到静态页面
41 res.sendRedirect(req.getContextPath() + "/html/" + staticPath);
42 return;
43 }
44 // 如果静态页面不存在
45 // 3. 生成静态页面
46 // 创建静态页面的路径
47 String staticPath = key + ".html";
48 // 获取真实路径
49 String realPath = sc.getRealPath("/html/" + staticPath);
50 // 创建自定义Response
51 StaticResponse sr = new StaticResponse(res, realPath);
52 // 放行
53 chain.doFilter(request, sr);
54 // 保存静态页面到map中
55 staticMap.put(key, staticPath);
56 // 4. 重定向到静态页面
57 res.sendRedirect(req.getContextPath() + "/html/" + staticPath);
58 }
59 public void init(FilterConfig fConfig) throws ServletException {
60 this.config = fConfig;
61 }
62 }
编写完Filter后,要在web.xml中配置StaticFilter,配置如下:
<filter>
<filter-name>StaticFilter</filter-name>
<filter-class>cn.itcast.chapter04.filter.StaticFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>StaticFilter</filter-name>
<servlet-name>BookServlet</servlet-name>
</filter-mapping>
需要注意的是,在运行程序前,要在WebContent根目录下创建html文件夹,以便存放生成的静态页面。重新启动Web服务器,打开IE浏览器,在地址栏中输http://localhost:8080/chapter04/index_book.jsp
,查询结果如图4所示。
图4 查询结果
这时查看JavaSE分类,查询结果如图5所示。
图5 查询结果
在图5中,可以看到浏览器的URL路径变为访问静态页面了,说明完成了动态页面静态化的功能。需要注意的是,页面静态化虽然可以提高程序的性能,但是却脱离了查询数据库,当数据库发生变化时,用户无法获得最新的数据。这时可以在对数据库进行操作时添加一个标记,告诉程序什么时候需要查询数据库,此功能在这里就不详细讲解了,有兴趣的同学可以自行研究。