JavaWeb之過濾器


 

時間:

 

Talk is cheap  Show me the code 


JavaWeb三大組件:
    Servlet、Listener、Filter
    都需要在web.xml中進行配置,Listener中的兩個感知監聽器不需要配置。


——過濾器概述

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

    當用戶請求某個Servlet或其他資源(JSP、css、html)時,會先執行部署在這個請求上的Filter,如果Filter“放行”,那么會繼續執行用戶請求的資源,如果Filter“不放行”,那么就不會執行用戶請求的資源。

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


——編寫一個過濾器

Filter是單例的。

1、編寫一個類,並且實現Filter接口

import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
 
/**
 * 編寫過濾器
 *  1、寫一個類,並且實現Filter接口
 *  2、在web.xml中進行相關配置
 * 
 * @author 31067
 * 
 */
public class AFilter implements Filter {
    /**
     * 在銷毀之前執行,用來對非內存資源進行釋放
     */
    @Override
    public void destroy() {
        System.out.println("銷毀");
    }
 
    /**
     * 每次過濾時都會執行
     */
    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain) throws IOException, ServletException {
        System.out.println("攔截");
        chain.doFilter(request, response);
        System.out.println("放行了");
    }

    /**
     * 在Filter創建后馬上調用,用來做初始化操作
     */
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        System.out.println("出生");
    }
}

2、在web.xml文件中進行配置

    <filter>
        <filter-name>xxx</filter-name>
        <filter-class>com.wyc.web.filter.AFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>xxx</filter-name>
        <url-pattern>/*</url-pattern>    //通常會使用通配符進行url-pattern的配置  /web/*
    </filter-mapping> 



——Filter的生命周期

1、Filter接口生命周期方法:

    *   void init(FilterConfig config)
        在服務器啟動時會創建Filter的實例對象,並且每個類型的Filter只會創建一個實例對象,也就是說Filter是單例的。
        在創建完Filter實例后,會馬上調用init()方法完成初始化操作,這個方法只會被執行一次。

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

    *   void doFilter(ServletRequest , ServletResponse , FilterChain)
        這個方法會在用戶每次訪問“目標資源<url-pattern>/index.jsp</url-pattern>”時執行。
        如果需要“放行”,那么需要調用FilterChain對象的doFilter(ServletRequest, ServletResponse)方法,如果不調用該方法,則無法請求目標資源。 



2、生命周期方法中的參數

    FilterConfig:
        *   獲取初始化參數:getInitParameter();
        *   獲取所有初始化參數的名稱:Enumeration getInitParameterNames()。
        *   獲取過濾器名稱:getFilterName();
        *   獲取application:getServletContext();(獲取當前上下文對象)

    FilterChain:
        *   doFilter(ServletRequest, ServletResponse)
            執行“放行”功能,使請求可以訪問到資源。
            相當於調用了請求Servlet的Service方法。
            如果當前過濾器是最后一個過濾器,那么調用chain.doFilter()方法表示執行目標資源,如果不是最后一個過濾器,那么chain.doFilter()表示執行下一個過濾器的doFilter()方法。

    面試:Filter接口的doFilter()方法和FilterChain的doFilter()方法有什么區別?




——過濾器的四種攔截方式 

攔截方式:
    *   請求:DISPATCHER
    *   轉發:FORWARD
    *   包含:INCLUDE
    *   錯誤:ERROR

    如果不寫,則默認<dispatcher>REQUEST</dispatcher>,如果只寫一種,則默認攔截方式就不存在了。

攔截方式需要在<filter-mapping>中進行配置:

    <filter-mapping>
        <filter-name>BFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>    // 默認攔截方式
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>


——過濾器的執行順序


<filter-mapping>中的配置執行順序決定了過濾器的執行順序。

    先配置先執行。
    XML配置如下:
    <web-app>

        <filter>
            <filter-name>AFilter</filter-name>
            <filter-class>com.wyc.web.filter.AFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>AFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
 
        <filter>
            <filter-name>BFilter</filter-name>
            <filter-class>com.wyc.web.filter.BFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>BFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    </web-app> 


    執行效果如下:(與棧相似,當chain的doFilter()方法執行完畢后,還會繼續執行剩余代碼)
        AFilter...start

        BFilter...start
        index.jsp
        BFilter...end
        AFilter...end


——Filter的應用場景

1、執行目標資源之前做預處理工作,例如設置編碼,這種操作通常都會放行,只是在目標資源執行之前做一些准備工作。
    幾乎在所有的Servlet中都需要寫:request.setCharacterEncoding();,這就可以把代碼放到過濾器中。

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

3、在目標資源執行后,做一些后續的特殊處理工作,例如把目標資源輸出的數據進行處理。
    也稱為回程攔截。


——Filter設置目標資源

在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>com.wyc.servlet.MyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/MyServlet</url-pattern>
    </servlet-mapping>
 
    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.wyc.filter.MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyFilger</filter-name>
        <servlet-name>MyServlet</servlet-name>    // 訪問指定的Servlet
    </filter-mapping>

    當用戶訪問:localhost:8080/FilterDemo/MyServlet時,會執行名稱為MyServlet的Servlet,這時會執行綁定的過濾器。 


    <filter-mapping>中可以寫以下四種標簽:
        <filter-name>
        <url-pattern>
        <dispatcher>
        <servlet-name>

——Filter小結

1、Filter接口的三個方法:
    *   void init(FilterConfig config)
        在Tomcat啟動時被調用。

    *   void destory()
        在Tomcat關閉時被調用。

    *   void doFilter(ServletRequest, ServletResponse, FilterChain)
        每次請求時都會調用該方法。


2、FilterConfig類
    與ServletConfig相似,用來獲取Filter的初始化參數。
    *   ServletContext getServletContext()
        獲取Servlet上下文對象。

    *   String getFilterName()
        獲取Filter的配置名稱。

    *   String getInitParameter(String name)
        獲取Filter的初始化參數,與<init-param>元素對應。

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

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


4、四中攔截方式:
    REQUEST:攔截直接請求方式
    FORWARD:攔截請求轉發方式
    INCLUDE:攔截請求包含方式
    ERROR:攔截錯誤轉發方式

    默認是REQUEST。

——分IP統計網站的訪問次數

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

分析:
    因為一個網站可能有多個頁面,無論哪個頁面被訪問,都要統計訪問次數,所以使用過濾器最為方便。
    因為需要分IP進行統計,所以可以在過濾器中創建一個Map,使用IP作為key,訪問次數為value。當有用戶訪問時,獲取請求的IP,如果IP在Map中存在,說明以前訪問過,那么在訪問次數上+1即可,如果IP在Map中不存在,那么設置value為1。

    整個網站只需要一個Map即可。
    Map什么時候創建,將Map保存到那里?
        >   Map需要在Filter中使用,也要在頁面中使用,所以保存到ServletContext中(application)
        >   可以放到ServletContextListener監聽器中,在服務器啟動時完成創建,並保存到ServletContext中。

代碼:

監聽器:


public class IPListener implements ServletContextListener {
 
    /**
     * 在服務器啟動時創建Map,保存到ServletContext中
     */
    public void contextInitialized(ServletContextEvent sce) {
        // 創建Map
        Map<String, Integer> map = new LinkedHashMap<String, Integer>();
 
        // 得到ServletContext
        ServletContext sc = sce.getServletContext();
 
        // 將map保存到ServletContext中
        sc.setAttribute("ipMaps", map);
    }
 
    public void contextDestroyed(ServletContextEvent arg0) {
 
    }
}

過濾器:

/**
 * 從ServletContext中獲取Map對象
 * 從request中得到請求客戶端的IP
 * 進行統計工作,將結果保存到Map中
 * 
 * @author 31067
 * 
 */
public class IPFilter implements Filter {
    private FilterConfig config;
    public void destroy() {
 
    }
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        /*
         * 1、得到ServletContext中的Map
         * 2、從request中獲取當前客戶端的IP地址
         * 3、查看Map中是否存在這個IP對應的訪問次數,如果存在,把次數+1再保存
         * 4、如果IP不存在,那么說明是第一次訪問,設置訪問次數為1
         */
 
        // 1、得到ServletContext
        ServletContext app = this.config.getServletContext();
        // 得到Map
        Map<String, Integer> map = (Map<String, Integer>) app.getAttribute("ipMaps");
 
        /*
         * 2、獲取客戶端的IP地址
         */
        String ip = request.getRemoteAddr();
 
        /*
         * 3、進行判斷
         */
        if (map.get(ip) == null) {
            map.put(ip, 1);
        } else {
            int count = map.get(ip);
            map.put(ip, count + 1);
        }
        chain.doFilter(request, response);
    }
 
    /**
     * 在服務器啟動時就會執行本方法,而且本方法只執行一次
     */
    public void init(FilterConfig config) throws ServletException {
        this.config = config;
    }
 



——粗粒度權限控制

攔截是否登錄、攔截用戶名、權限攔截。

基於角色的訪問控制(Role-Based Access Control)作為傳統訪問控制(自主訪問,強制訪問)的有前景的代替受到廣泛的關注。在RBAC中,權限與角色相關聯,用戶通過成為適當角色的成員而得到這些角色的權限。這就極大地簡化了權限的管理。在一個組織中,角色是為了完成各種工作而創造,用戶則依據它的責任和資格來被指派相應的角色,用戶可以很容易地從一個角色被指派到另一個角色。角色可依新的需求和系統的合並而賦予新的權限,而權限也可根據需要而從某角色中回收。角色與角色的關系可以建立起來以囊括更廣泛的客觀情況。

說明:
    給出三個頁面:index.jsp  user.jsp  admin.jsp
    *   index.jsp:任何用戶都可以訪問,沒有限制。
    *   user.jsp:只有登錄用戶才能訪問。
    *   admin.jsp:只有管理員才能訪問。

代碼:

Servlet:

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");
        /*
         * 1、獲取用戶名
         * 2、判斷用戶名中是否包含wyc
         *   >  如果包含,就是管理員
         *   >  如果不包含,就是普通會員
         * 3、要把登錄的用戶名稱保存到Session域中
         * 4、轉發到index.jsp
         */
        String username = null;
        String n = request.getParameter("username");
 
        // 判斷用戶名是否為空並且是否為空字符串
        if(n != null && !n.trim().isEmpty()){
            username = n;
        }
 
        // 如果用戶名不為null,則進行判斷,避免空指針異常
        if(username != null && username.contains("wyc")){
            request.getSession().setAttribute("admin", username);
        } else {
            request.getSession().setAttribute("username", username);
        }
        request.getRequestDispatcher("/index.jsp").forward(request, response);
    }
}


過濾器:

UserFilter:過濾/user/*路徑下的請求。

public class UserFilter implements Filter {
    public void destroy() {
    }
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 
        /*
         * 1、得到Session
         * 2、判斷session域中是否存在admin,如果存在,則放行
         * 3、判斷session域中是否存在username,如果存在,則放行
         * 4、如果都不存在,進行攔截操作,並將頁面轉發到index.jsp
         */
        HttpServletRequest req = (HttpServletRequest)request;
        String name = (String)req.getSession().getAttribute("admin");
 
        if(name != null) {
            chain.doFilter(request, response);
            return;
        }
        name = (String)req.getSession().getAttribute("username");
        if(name != null) {
            chain.doFilter(request, response);
            return;
        } else {
            request.setAttribute("msg", "請先登錄");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }
 
    public void init(FilterConfig fConfig) throws ServletException {
    }
}



AdminFilter:過濾/admin/*路徑下的請求。

public class AdminFilter implements Filter {
 
    public void destroy() {
    }
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        /*
         * 1、得到session
         * 2、判斷session域中是否存在admin,如果存在,則放行
         */
        HttpServletRequest req = (HttpServletRequest)request;
        String name = (String)req.getSession().getAttribute("admin");
        if(name != null) {
            chain.doFilter(request, response);
            return;
        } else {
            request.setAttribute("msg", "管理員,請先登錄");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }
 
    public void init(FilterConfig fConfig) throws ServletException {
    }
}
 


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


POST請求:
    >   request.setCharacterEncoding("utf-8");
GET請求:
    >   String username = request.getParameter("username");
    >   username = new String(username.getBytes("iso-8859-1"), "utf-8");

過濾器:

Encodingfilter:

public class EncodingFilter implements Filter {
 
    public void destroy() {
    }
 
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 處理POST請求編碼
        request.setCharacterEncoding("utf-8");
 
        /*
         * 處理GET請求的編碼問題
         */
 
        /*
         * 將原請求對象轉換為自己的HttpServletRequest對象
         * 1、寫一個request的裝飾類
         * 2、增強getParameter()方法
         * 3、在“放行”時使用自己的request對象
         */
 
        /*
         * 判斷請求方式
         * 不同的請求方式應該使用不同的編碼處理方式
         */
        HttpServletRequest req = (HttpServletRequest)request;
 
        if(req.getMethod().equals("GET")){
            EncodingRequest er = new EncodingRequest(req);
            /*
             * 將實現了HttpServletRequest接口的對象傳入
             */
            chain.doFilter(er, response);
        } else if(req.getMethod().equals("POST")) {
            chain.doFilter(request, response);
        }
    }
 
    public void init(FilterConfig fConfig) throws ServletException {
    }
}


HttpServletRequest裝飾類:

可以將此類保存為工具類,只需要在web.xml中進行配置即可使用。

/**
 * 裝飾request
 * 
 * @author 31067
 * 
 */
public class EncodingRequest extends HttpServletRequestWrapper {
    private HttpServletRequest request;
 
    public EncodingRequest(HttpServletRequest request) {
        /*
         * 父類實現了“一切拜托你”
         */
        super(request);
        this.request = request;
    }
 
    @Override
    public String getParameter(String name) {
        String value = request.getParameter(name);
        /*
         * 處理編碼問題
         */
        try {
            value = new String(value.getBytes("iso-8859-1"), "utf-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return value;
    }
}



——頁面靜態化

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

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


步驟:

1、寫一個小項目,圖書管理系統
    頁面:
        *   jsp:index.jsp
            鏈接頁面,有四個超鏈接。
            >   查詢所有
            >   查看SE分類
            >   查看EE分類
            >   查看框架分類
        *   show.jsp
            顯示查詢結果。

    Servlet:
        BookServlet
            >   findAll()
                查看所有圖書
            >   findByCategory(int category)
                按分類查詢

    BookService:
        略

    BookDao:
        >   List<Book> findAll()
        >   List<Book> findByCategory(int category)

    domain:
        Book類

    SQL腳本:

        create database bookdb;
 
        create table book (
            id char(32) primary key,
            name varchar(100),
            price numeric(10, 2),
            category int
        );
 
        insert into book values ('b1', 'JavaSE_1', 10, 1);
        insert into book values ('b2', 'JavaSE_2', 15, 1);
        insert into book values ('b3', 'JavaSE_3', 20, 1);
        insert into book values ('b4', 'JavaSE_4', 25, 1);
 
        insert into book values ('b5', 'JavaEE_1', 30, 2);
        insert into book values ('b6', 'JavaEE_2', 35, 2);
        insert into book values ('b7', 'JavaEE_3', 40, 2);
 
        insert into book values ('b8', 'Java_frameword_1', 45, 3);
        insert into book values ('b9', 'Java_frameword_2', 50, 3);
 
        select * from book;

===============================================================================

com.wyc.book.dao.BookDao

import java.sql.SQLException;
import java.util.List;
 
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
 
import com.wyc.book.domain.Book;
import com.wyc.jdbc.TxQueryRunner;
 
public class BookDao {
    private QueryRunner qr = new TxQueryRunner();
 
    public List<Book> findAll() {
 
        String sql = "select * from book";
 
        try {
            return qr.query(sql, new BeanListHandler<Book>(Book.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
 
    public List<Book> findByCategory(int category) {
 
        String sql = "select * from book where category = ?";
        Object[] params = { category };
 
        try {
            return qr.query(sql, new BeanListHandler<Book>(Book.class), params);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}


===============================================================================

com.wyc.book.domain.Book

public class Book {
    private String id;
    private String name;
    private double price;
    private int category;
 
    @Override
    public String toString() {
        return "Book [id=" + id + ", name=" + name + ", price=" + price + ", categroy=" + category + "]";
    }
 
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public double getPrice() {
        return price;
    }
 
    public void setPrice(double price) {
        this.price = price;
    }
 
    public int getCategory() {
        return category;
    }
 
    public void setCategory(int category) {
        this.category = category;
    }
}

===============================================================================

com.wyc.book.service.BookService

import java.util.List;
 
import com.wyc.book.dao.BookDao;
import com.wyc.book.domain.Book;
 
public class BookService {
 
    private BookDao bookDao = new BookDao();
 
    public List<Book> findAll(){
        return bookDao.findAll();
    }
 
    public List<Book> findByCategory(int category){
        return bookDao.findByCategory(category);
    }
}

===============================================================================

com.wyc.book.web.servlet.BookServlet

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import com.wyc.book.service.BookService;
import com.wyc.servlet.BaseServlet;
 
public class BookServlet extends BaseServlet {
 
    private BookService bookService = new BookService();
 
    public String findAll(HttpServletRequest request, HttpServletResponse response) {
        request.setAttribute("bookList", bookService.findAll());
        return "f:show.jsp";
    }
 
    public String findByCategory(HttpServletRequest request, HttpServletResponse response) {
        String value = request.getParameter("category");
        int category = Integer.parseInt(value);
        request.setAttribute("bookList", bookService.findByCategory(category));
        return "f:show.jsp";
    }
}

===============================================================================

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
 
<c3p0-config>
    <!-- 默認配置 -->
    <default-config>
        <!-- 連接四大參數配置 -->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/bookdb</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">Admin123</property>
        <!-- 連接池參數配置 -->
        <property name="acquireIncrement">3</property>
        <property name="initialPoolSize">10</property>
        <prpperty name="minPoolSize">2</prpperty>
        <property name="maxPoolSize">10</property>
    </default-config>
</c3p0-config>

===============================================================================

index.jsp

<body>
    <h1>鏈接頁面</h1>
    <a href="<c:url value='/BookServlet?method=findAll' />">查詢所有</a>
    <a href="<c:url value='/BookServlet?method=findByCategory&category=1' />">查詢SE</a>
    <a href="<c:url value='/BookServlet?method=findByCategory&category=2' />">查詢EE</a>
    <a href="<c:url value='/BookServlet?method=findByCategory&category=3' />">查詢Framework</a>
</body>

===============================================================================

show.jsp

<body>
    <h1 align="center">圖書列表</h1>
    <table border="1" align="center" width="50%">
        <tr>
            <th>書名</th>
            <th>單價</th>
            <th>分類</th>
        </tr>
    <c:forEach items="${requestScope.bookList }" var="book">
        <tr>
            <td>${book.name }</td>
            <td>${book.price }</td>
            <c:if test="${book.category eq 1 }">
                <td><font color="green">JavaSE</font></td>
            </c:if>
            <c:if test="${book.category eq 2 }">
                <td><font color="red">JavaEE</font></td>
            </c:if>
            <c:if test="${book.category eq 3 }">
                <td><font color="blue">JavaFramework</font></td>
            </c:if>
        </tr>
    </c:forEach>
    </table>
</body>

===============================================================================
 


2、頁面靜態化

首次訪問時去數據庫獲取數據,然后把數據保存到HTML頁面中。
第二次訪問時,就不再去訪問數據庫了,而是直接顯示HTML。

給出一個過濾器,把Servlet請求的資源所做的輸出,保存到HTML中,然后再重定向到HTML頁面,當第二次訪問時,這個HTML已經存在,那么直接重定向即可,不需要再次訪問Servlet了。




com.wyc.book.web.filter.StaticFilter

import java.io.File;
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class StaticFilter implements Filter {
 
    private FilterConfig config;
 
    public void destroy() {
    }
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse resp = (HttpServletResponse)response;
 
        /*
         * 1、第一次訪問時,要查找請求對應的HTML頁面是否存在,如果存在,則重定向到指定頁面
         * 2、如果HTML不存在,則放行,把Servlet訪問數據庫后輸出給客戶端的數據保存到一個HTML頁面中,再重定向到HTML頁面
         */
 
        /*
         * 一、獲取Category參數,來判斷請求的頁面
         *   category有四種可能:
         *   >   null -->null.html
         *   >   1 --> 1.html
         *   >   2 --> 2.html
         *   >   3 --> 3.html
         * HTML頁面保存的路徑,在htmls目錄下
         * 
         * 判斷對應的HTML文件是否存在,如果存在,直接重定向
         */
        String category = request.getParameter("category");
        // 得到對應的文件名稱
        String htmlPage = category + ".html";
 
        // 得到文件的存放目錄,獲取的是真實目錄
        String htmlPath = config.getServletContext().getRealPath("/htmls");

        // 創建文件
        File destFile = new File(htmlPath, htmlPage);
 
        // 如果文件存在,則重定向到該文件
        if(destFile.exists()) {
            // 重定向,重定向要使用完整的絕對路徑
            resp.sendRedirect(req.getContextPath() + "/htmls/" + htmlPage);
            return;
        }
 
        /*
         * 二、如果HTML文件不存在,需要生成HTML頁面並保存
         * 1、放行,show.jsp頁面會做出輸出操作,可以將輸出的內容輸出到指定的HTML頁面,而不是輸出到客戶端
         * 2、調包response,讓它的getWriter()與一個HTML文件綁定,而不是客戶端,那么show.jsp的輸出操作就輸出到了指定的HTML文件中了
         */
 
        // 傳遞文件的絕對路徑
        StaticResponse sr = new StaticResponse(resp, destFile.getAbsolutePath());
 
        /*
         * 放行,即生成了HTML文件
         * 
         * 在放行(輸出)之前,需要解決亂碼問題:
         *   因為所有的數據都是通過show.jsp頁面進行輸出的,所以可以在show.jsp頁面中添加如下meta標簽
         *   <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
         */
        chain.doFilter(request, sr);
 
        // 此時HTML文件已經存在了,可以重定向到指定頁面了
        sr.sendRedirect(req.getContextPath() + "/htmls/" + htmlPage);
    }
 
    public void init(FilterConfig fConfig) throws ServletException {
        this.config = fConfig;
    }
}



===============================================================================

com.wyc.book.web.filter.StaticResponse

HttpServletResponse裝飾類 


import java.io.PrintWriter;
 
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
 
public class StaticResponse extends HttpServletResponseWrapper {
 
    private PrintWriter pw;
 
    /*
     * String path:指向HTML文件的路徑
     */
    public StaticResponse(HttpServletResponse response, String path) {
        super(response);
 
        try {
 
            // 創建一個HTML文件並創建一個與HTML文件路徑綁定在一起的流對象
            this.pw = new PrintWriter(path, "utf-8");
 
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
 
    public PrintWriter getWriter(){
        // 返回一個與HTML綁定在一起的printWriter對象
        // JSP會使用它進行輸出,這樣數據都會輸出到指定的HTML文件中了
        return pw;
    }
}


免責聲明!

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



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