27、Filter開(kāi)發(fā)(3)(包裝request和response)(JavaEE筆記)

主要內(nèi)容:

  • filter的部署
  • 包裝設(shè)計(jì)模式對(duì)request和response進(jìn)行包裝增強(qiáng)

一、Filter的部署-注冊(cè)Filter

  • <filter-name>:指定過(guò)濾器的名字,內(nèi)容不能為空。

  • <filter-class>: 指定過(guò)濾器的完整的限定類(lèi)名

  • <init-param> 指定初始化參數(shù),其子元素<param-name>指定參數(shù)的名字,<param-value>指定參數(shù)的值。可以使用FilterConfig接口對(duì)象來(lái)訪(fǎng)問(wèn)初始化參數(shù)。

  • <filter-mapping> 設(shè)置一個(gè)Filter所負(fù)責(zé)攔截的資源。一個(gè)Filter攔截的資源可以通過(guò)兩種方式來(lái)指定:Servlet名稱(chēng)和資源訪(fǎng)問(wèn)的請(qǐng)求路徑。

    • <filter-name>子元素用于設(shè)置Filter注冊(cè)名稱(chēng),和上面一樣。
    • <url-pattern>設(shè)置Filter所攔截的請(qǐng)求路徑;/表示攔截所有,.jsp表示攔截jsp文件。等等。
    • <dispatcher>指定過(guò)濾器所攔截的資源被Servlet容器調(diào)用的方式,可以是REQUEST,INCLUDE,F(xiàn)ORWARD,ERROR之一(必須是大寫(xiě)),默認(rèn)是REQUEST。用戶(hù)可以設(shè)置多個(gè)<dispatcher>子元素來(lái)指定Filter對(duì)資源的多種調(diào)用方式進(jìn)行攔截。
  • <dispatcher>子元素可以設(shè)置的值及其意義

    • REQUEST:當(dāng)用戶(hù)直接訪(fǎng)問(wèn)頁(yè)面時(shí),web容器將會(huì)調(diào)用過(guò)濾器。如果目標(biāo)資源是通過(guò)RequestDispatcher的include或forward方法訪(fǎng)問(wèn)時(shí),那么該過(guò)濾器就不會(huì)被調(diào)用。
    • INCLUDE:如果目標(biāo)資源是通過(guò)RequestDispatcher的include方法訪(fǎng)問(wèn)時(shí),那么該過(guò)濾器將被調(diào)用,除此之外,該過(guò)濾器將不會(huì)被調(diào)用。
    • FORWARD:如果目標(biāo)資源是通過(guò)RequestDispatcher的forward方法訪(fǎng)問(wèn)時(shí),那么該過(guò)濾器將被調(diào)用,除此之外,該過(guò)濾器將不會(huì)被調(diào)用。
    • ERROR:如果目標(biāo)資源是通過(guò)聲明式異常處理機(jī)制調(diào)用時(shí),那么該過(guò)濾器將被調(diào)用。除此之外,過(guò)濾器不會(huì)被調(diào)用。

示例:
FilterDemo4.java

package cn.itcast.web.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;

public class FilterDemo4 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        System.out.println("hahah");
    }
    @Override
    public void destroy() {
    }
}

配置:

<filter>
    <filter-name>FilterDemo4</filter-name>
    <filter-class>cn.itcast.web.filter.FilterDemo4</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>FilterDemo4</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>ERROR</dispatcher>
  </filter-mapping>
  <error-page>
    <exception-type>java.lang.ArithmeticException</exception-type>
    <location>/2.jsp</location>
  </error-page>

1.jsp

<body>
    <%
        int x = 1/0;
     %>
  </body>

2.jsp

<body>
error page
</body>

說(shuō)明:

  • 1.本來(lái)我們是在1.jsp中配置errorPage="/2.jsp",但是這樣配置不起作用。于是我們?cè)?code>web.xml中進(jìn)行配置。試驗(yàn)時(shí)我們?cè)L問(wèn)1.jsp,那么就會(huì)被過(guò)濾器攔截下來(lái),不會(huì)到達(dá)2.jsp。
  • 2.在以后的開(kāi)發(fā)中我們經(jīng)常要攔截forward轉(zhuǎn)發(fā)的資源,注意在配置文件中進(jìn)行配置,不然是不會(huì)起作用的。在上個(gè)例子中我們可以看到。

二、filter高級(jí)開(kāi)發(fā)

由于開(kāi)發(fā)人員在Filter中可以得到代表用戶(hù)請(qǐng)求和響應(yīng)的request、response對(duì)象,因此在編程中可以使用Decorator(裝飾器)模式對(duì)這些對(duì)象進(jìn)行包裝,再把包裝后的對(duì)象傳給目標(biāo)資源,從而實(shí)現(xiàn)一些特殊需求。

注意:有四種方式訪(fǎng)問(wèn)web資源,正常的通過(guò)瀏覽器直接訪(fǎng)問(wèn)(request方式)、forward方式、include方式和error方式。

2.1回顧包裝開(kāi)發(fā)模式

之前我們?cè)诠P記20中講到過(guò)包裝開(kāi)發(fā)模式,這里再次回顧一下。使用包裝設(shè)計(jì)模式對(duì)BufferedReader類(lèi)進(jìn)行包裝增強(qiáng)。

BufferedReaderWrapper.java

package cn.itcast.demo;

import java.io.BufferedReader;
import java.io.IOException;
//使用包裝設(shè)計(jì)模式對(duì)BufferedReader類(lèi)進(jìn)行包裝增強(qiáng)
/*
 * 1.實(shí)現(xiàn)與被增強(qiáng)對(duì)象相同的接口,如果接口方法太多,也可以繼承一個(gè)類(lèi)
 * 2.定義一個(gè)變量記住被增強(qiáng)對(duì)象
 * 3.定義一個(gè)構(gòu)造器,接收被增強(qiáng)對(duì)象
 * 4.覆蓋需要增強(qiáng)的方法
 * 5.對(duì)于不想增強(qiáng)的方法,直接調(diào)用被增強(qiáng)對(duì)象的方法
 * 
 * */
import java.io.Reader;

public class BufferedReaderWrapper extends BufferedReader {
    private BufferedReader br;
    private int linenum = 1;

    public BufferedReaderWrapper(BufferedReader br) {
        super(br);
        // 子類(lèi)在使用構(gòu)造函數(shù)的時(shí)候會(huì)調(diào)用父類(lèi)的構(gòu)造函數(shù),但是
        // 這里不知道調(diào)用父類(lèi)哪個(gè)構(gòu)造函數(shù),于是就調(diào)用無(wú)參構(gòu)造
        // 函數(shù),但是父類(lèi)又沒(méi)有無(wú)參構(gòu)造函數(shù),這樣就會(huì)報(bào)錯(cuò),所
        // 以這里我們要指定調(diào)用父類(lèi)哪個(gè)構(gòu)造函數(shù)
        this.br = br;
    }

    @Override
    public String readLine() throws IOException {// 增強(qiáng)此方法
        String line = br.readLine();
        if (line == null) {
            return line;
        }
        return linenum++ + ":" + line;
    }
}

測(cè)試:TestBufferedReaderWrapper.java

package cn.itcast.demo;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class TestBufferedReaderWrapper {

    public static void main(String[] args) throws IOException {
        
        BufferedReader br = new BufferedReader(new FileReader("src/cn/itcast/demo/BufferedReaderWrapper.java"));
        BufferedReaderWrapper wrapper = new BufferedReaderWrapper(br);
        
        /*String line = null;
        while((line = wrapper.readLine()) != null ){
            System.out.println(line);
        }*/
        FileWriter fw = new FileWriter("D:\\1.java");
        String line = null;
        while((line = wrapper.readLine()) != null ){
            fw.write(line + "\r\n");//注意要換行
        }
        fw.close();
        wrapper.close();
        br.close();
    }
}

說(shuō)明:BufferedReader類(lèi)中的readLine方法可以讀取一行文本,這里我們想讓readLine讀取一行文本時(shí)還在本行最前面加上行號(hào),于是我們使用包裝設(shè)計(jì)模式對(duì)此方法進(jìn)行包裝。注意:這里當(dāng)此方法讀到空行的時(shí)候返回的是"",而不是null。
記住:能用包裝設(shè)計(jì)模式就不要用子類(lèi)的方式

2.2Decorator(包裝)設(shè)計(jì)模式

  • 當(dāng)某個(gè)對(duì)象的方法不適應(yīng)業(yè)務(wù)需求時(shí),通常有兩種方式可以對(duì)方法進(jìn)行增強(qiáng):

    • 編寫(xiě)子類(lèi),覆蓋需要增強(qiáng)的方法
    • 使用Decorator設(shè)計(jì)模式對(duì)方法進(jìn)行增強(qiáng)
    • 使用動(dòng)態(tài)代理(這里先不講)
  • 在實(shí)際開(kāi)發(fā)中遇到需要增強(qiáng)對(duì)象的方法時(shí),到底選擇用哪種方式

    • 沒(méi)有具體的定式,不過(guò)有一種情況下,必須使用Decorator設(shè)計(jì)模式,即被增強(qiáng)對(duì)象,開(kāi)發(fā)人員只能得到它的對(duì)象,無(wú)法得到它的class文件。
    • 比如request、response對(duì)象,開(kāi)發(fā)人員之所以在Servlet中能通過(guò)sun公司定義的HttpServletRequest\HttpServletResponse接口去操作這些對(duì)象,是因?yàn)閠omcat服務(wù)器廠(chǎng)商編寫(xiě)了request、response接口的實(shí)現(xiàn)類(lèi)。Web服務(wù)器在調(diào)用Servlet時(shí),會(huì)用這些接口的實(shí)現(xiàn)類(lèi)創(chuàng)建出對(duì)象,然后傳遞給Servlet程序。
    • 此種情況下,由于開(kāi)發(fā)人員根本不知道服務(wù)器廠(chǎng)商編寫(xiě)的request、response接口的實(shí)現(xiàn)類(lèi)是哪個(gè),在程序中只能拿到其提供的對(duì)象,因此就只能采用Decorator設(shè)計(jì)模式對(duì)這些對(duì)象進(jìn)行增強(qiáng)。
  • Decorator設(shè)計(jì)模式的實(shí)現(xiàn)
    1.首先看需要被增強(qiáng)對(duì)象繼承了什么接口或父類(lèi),編寫(xiě)一個(gè)類(lèi)也去繼承這些接口或父類(lèi)。
    2.在類(lèi)中定義一個(gè)變量,變量類(lèi)型即需要增強(qiáng)對(duì)象類(lèi)型。
    3.在類(lèi)中定義一個(gè)構(gòu)造函數(shù),接收需要增強(qiáng)的對(duì)象。
    4.覆蓋需要增強(qiáng)的方法,編寫(xiě)增強(qiáng)的代碼。
    使用此設(shè)計(jì)模式為BufferedReader類(lèi)的readLine方法添加行號(hào)的功能。在上面的例子中我們可以看到。

三、對(duì)request對(duì)象的增強(qiáng)(工程day18_2

  • ServletAPI中提供了一個(gè)request對(duì)象的Decorator設(shè)計(jì)模式的默認(rèn)實(shí)現(xiàn)類(lèi)HttpServletRequestWrapperHttpServletRequestWrapper類(lèi)實(shí)現(xiàn)了request接口中的所有方法,但這些方法的內(nèi)部實(shí)現(xiàn)都是僅僅調(diào)用了一下所包裝的request對(duì)象的對(duì)應(yīng)方法)以避免用戶(hù)在對(duì)request對(duì)象進(jìn)行增強(qiáng)時(shí)需要實(shí)現(xiàn)request接口中的所有方法(這樣太麻煩)。
  • 使用Decorator模式包裝request對(duì)象,完全解決get、post請(qǐng)求方式下的亂碼問(wèn)題(前面有過(guò)這樣一個(gè)例子,但是那個(gè)例子中只能解決post方式下的亂碼問(wèn)題)。

3.1 包裝request對(duì)象,解決亂碼問(wèn)題

過(guò)濾器:CharacterEncodingFilter.java

public class CharacterEncodingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        // 這里我們先解決post方式的亂碼問(wèn)題
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        // 解決get方式的亂碼問(wèn)題
        MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(
                request);

        chain.doFilter(requestWrapper, response);
    }

    @Override
    public void destroy() {
    }
}

class MyCharacterEncodingRequest extends HttpServletRequestWrapper {
    // 這里我們使用一個(gè)變量記住Servlet傳遞的request對(duì)象。
    private HttpServletRequest request;

    public MyCharacterEncodingRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
    }

    @Override
    // 增強(qiáng)此方法,此方法得到表單提交的數(shù)據(jù)
    public String getParameter(String name) {
        String value = this.request.getParameter(name);
        if (value == null) {// 如果為空直接返回空即可
            return null;
        }
        if (!this.request.getMethod().equalsIgnoreCase("get")) {
            // 如果不是get方式則沒(méi)必要轉(zhuǎn)換
            return value;
        }
        try {
            /*
             * value = new
             * String(value.getBytes("ISO8859-1"),"UTF-8");//手工轉(zhuǎn)換,不要寫(xiě)死
             */

            value = new String(value.getBytes("ISO-8859-1"),
                    this.request.getCharacterEncoding());// 和Request設(shè)置的編碼一致
            return value;// 這里使用ISO-8859-1沒(méi)有解決亂碼,這里瀏覽器
            // 提交的本就是UTF-8編碼,不需要轉(zhuǎn)換
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

說(shuō)明:由于使用request.setCharacterEncoding("UTF-8");方式對(duì)get方式提交的數(shù)據(jù)無(wú)效,所以之前那個(gè)過(guò)濾器只能解決post方式提交的數(shù)據(jù)亂碼問(wèn)題。既然這種方式不能解決get方式提交的數(shù)據(jù)的亂碼問(wèn)題,那么我們可以將HttpServletRequest包裝之后再給用戶(hù)使用。這里我們主要是對(duì)其方法getParameter進(jìn)行增強(qiáng)。這樣就解決了get方式提交的亂碼問(wèn)題。

3.2 包裝request對(duì)象,實(shí)現(xiàn)html標(biāo)簽轉(zhuǎn)義功能

E:\apache\apache-tomcat-8.0.28-src\webapps\examples\WEB-INF\classes\util\HTMLFilter.java提供相應(yīng)的例子)
HtmlFilter .java

public class HtmlFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        MyHtmlRequest myHtmlRequest = new MyHtmlRequest(request);
        chain.doFilter(myHtmlRequest, response);
        
    }
    @Override
    public void destroy() {
    }
}
class MyHtmlRequest extends HttpServletRequestWrapper{
    
    private HttpServletRequest request;
    public MyHtmlRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
    }
    @Override
    public String getParameter(String name) {
        String value = this.request.getParameter(name);
        if(value == null){
            return null;
        }
        return filter(value);
    }
    
    public static String filter(String message) {

        if (message == null)
            return (null);

        char content[] = new char[message.length()];
        message.getChars(0, message.length(), content, 0);
        StringBuilder result = new StringBuilder(content.length + 50);
        for (int i = 0; i < content.length; i++) {
            switch (content[i]) {
            case '<':
                result.append("<");
                break;
            case '>':
                result.append(">");
                break;
            case '&':
                result.append("&");
                break;
            case '"':
                result.append(""");
                break;
            default:
                result.append(content[i]);
            }
        }
        return (result.toString());
    }
}

3.3 包裝request對(duì)象,實(shí)現(xiàn)對(duì)臟話(huà)進(jìn)行過(guò)濾

DirtyFilter.java

public class DirtyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        
        DirtyRequest dirtyRequest = new DirtyRequest(request);
        chain.doFilter(dirtyRequest, response);
    }
    @Override
    public void destroy() {
    }

}
class DirtyRequest extends HttpServletRequestWrapper{
    private List<String> dirtyWords = Arrays.asList("sb","畜生");
    private HttpServletRequest request ;
    public DirtyRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
    }
    @Override
    public String getParameter(String name) {
        String value = this.request.getParameter(name);
        if(value == null){
            return null;
        }
        for(String dirtyWord : dirtyWords){
            if(value.contains(dirtyWord)){
                value = value.replace(dirtyWord, "***");
            }
        }
        return value;
    }
}

四、包裝response對(duì)象

4.1 包裝response對(duì)象,實(shí)現(xiàn)壓縮響應(yīng)

GzipFilter.java

package cn.itcast.web.filter;
//解決全站的壓縮問(wèn)題,對(duì)Response進(jìn)行增強(qiáng),但是這個(gè)過(guò)濾器只是解決壓縮字符,需要把數(shù)據(jù)都寫(xiě)到內(nèi)存中去,如果是下載一個(gè)大文件,內(nèi)存可能會(huì)崩,這里我們可以在
//配置文件中設(shè)定只對(duì)字符壓縮有效,這攔截jsp,js,css,html
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class GzipFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        
        BufferResponse myResponse = new BufferResponse(response);
        chain.doFilter(request, myResponse);
        
        byte out[] = myResponse.getBuffer();//先獲得數(shù)據(jù)
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        GZIPOutputStream gout = new GZIPOutputStream(bout);//進(jìn)行壓縮
        gout.write(out);
        gout.close();//一定要關(guān)閉,這樣數(shù)據(jù)才會(huì)從緩存中寫(xiě)入到底層流中去
        
        byte gzip[] = bout.toByteArray();//從底層流中取得數(shù)據(jù)
        response.setHeader("content-encoding", "gzip");//這里需要告訴瀏覽器這是一個(gè)壓縮數(shù)據(jù)
        response.setContentLength(gzip.length);
        response.getOutputStream().write(gzip);//寫(xiě)出到瀏覽器
        
    }

    @Override
    public void destroy() {}

}

/*OutputStream out = response.getOutputStream();
out.write("aaaaaa".getBytes());

*之后servlet在使用Response的時(shí)候其實(shí)是使用我們自己定義的Response,而調(diào)用的getOutputStream方法也是我們自己定義的,這個(gè)方法返回的是
*MyServletOutputStream,然后調(diào)用write方法也是調(diào)用我們自己定義的write方法,此方法是將數(shù)據(jù)寫(xiě)到ByteArrayOutputStream底層流中。
*
*/


class BufferResponse extends HttpServletResponseWrapper{

    private HttpServletResponse response;
    private ByteArrayOutputStream bout = new ByteArrayOutputStream();//字節(jié)流
    private PrintWriter pw;
    
    public BufferResponse(HttpServletResponse response) {
        super(response);
        this.response = response;
    }
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        //這里我們對(duì)此方法進(jìn)行了增強(qiáng),不管是圖片還是文本等數(shù)據(jù)都會(huì)進(jìn)行壓縮,但是如果直接訪(fǎng)問(wèn)jsp卻不會(huì),因?yàn)閖sp一般是調(diào)用getWriter
        //方法,所以這里我們需要對(duì)getWriter方法進(jìn)行增強(qiáng)
        return new MyServletOutputStream(bout);
    }
    
    @Override
    public PrintWriter getWriter() throws IOException {
        
        /*return new PrintWriter(bout);//因?yàn)镻rintWriter有接受一個(gè)底層流的構(gòu)造函數(shù),所以這里我們不需要重寫(xiě)一個(gè),但是這個(gè)方法也是一個(gè)包裝類(lèi)
        //這個(gè)類(lèi)當(dāng)緩存沒(méi)有寫(xiě)滿(mǎn)的時(shí)候是不會(huì)講數(shù)據(jù)寫(xiě)到底層流中去,所以這里我們需要強(qiáng)制關(guān)閉此類(lèi)*/   
        //pw = new PrintWriter(bout);//jsp中的漢字是一個(gè)字符流,這里會(huì)將其先轉(zhuǎn)換為字節(jié)流,查的碼表是gb2312的碼表,但是我們?cè)O(shè)置的碼表是UTF-8
        //而PrintWriter有一個(gè)接受一個(gè)字符流的方法,而字符流就會(huì)有設(shè)置碼表的方法,而OutputStreamWriter是字符流到字節(jié)流的一個(gè)轉(zhuǎn)換流,里面就可以指定碼表
        pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
        return pw;
    }
    
    
    public byte[] getBuffer(){
        try{
            if(pw != null){
                pw.close();
            }
            if(bout != null){
                bout.flush();
                return bout.toByteArray();
            }
            return null;
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
class MyServletOutputStream extends ServletOutputStream{
    
    private ByteArrayOutputStream bout ;
    public  MyServletOutputStream(ByteArrayOutputStream bout) {
        this.bout = bout;
    }
    
    @Override
    public void write(int arg0) throws IOException {
        this.bout.write(arg0);
        
    }
    @Override
    public boolean isReady() {
        return false;
    }

    @Override
    public void setWriteListener(WriteListener listener) {}
}

說(shuō)明:

  • 這個(gè)包裝類(lèi)的實(shí)現(xiàn)有點(diǎn)難理解,這里我們?cè)敿?xì)說(shuō)明一下。這里是實(shí)現(xiàn)數(shù)據(jù)的壓縮之后再輸出給瀏覽器,而我們完全可以在servlet中先從緩存中拿到數(shù)據(jù)壓縮之后再輸出,但是那樣的話(huà)每個(gè)需要壓縮資源的servlet都需要編寫(xiě)重復(fù)的代碼,所以這里我們使用過(guò)濾器進(jìn)行簡(jiǎn)化。
  • 首先服務(wù)器將資源寫(xiě)給瀏覽器的時(shí)候(注意這里和request過(guò)濾器的方向是反的),會(huì)被這個(gè)瀏覽攔截到。攔截到之后我們對(duì)response進(jìn)行增強(qiáng)。
  • 而一般會(huì)調(diào)用getOutputStream方法和getWriter方法向?yàn)g覽器中寫(xiě)數(shù)據(jù),于是這里我們對(duì)這兩個(gè)方法進(jìn)行增強(qiáng)。
  • 方法getOutputStream會(huì)返回一個(gè)ServletOutputStream流,我們需要增強(qiáng),我們要讓此方法返回一個(gè)我們自己定義的一個(gè)流,于是對(duì)此類(lèi)也進(jìn)行包裝。
  • 方法getWriter中我們將自己定義的流傳遞給PrintWriter方法。
  • 我們將數(shù)據(jù)壓縮之后存入到底層流中,之后用戶(hù)在調(diào)用getOutputStream方法和getWriter時(shí)拿到的數(shù)據(jù)就是我們壓縮之后的數(shù)據(jù)。
  • 其實(shí)整個(gè)過(guò)程就是當(dāng)用戶(hù)調(diào)用方法向?yàn)g覽器輸出數(shù)據(jù)的時(shí)候我們將response的相關(guān)方法進(jìn)行增強(qiáng)(實(shí)現(xiàn)數(shù)據(jù)壓縮)之后再去調(diào)用真正response的方法進(jìn)行輸出,這樣就可以實(shí)現(xiàn)壓縮。

配置:

<filter>
    <filter-name>GzipFilter</filter-name>
    <filter-class>cn.itcast.web.filter.GzipFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>GzipFilter</filter-name>
    <url-pattern>*.jsp</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
    <filter-name>GzipFilter</filter-name>
    <url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>GzipFilter</filter-name>
    <url-pattern>*.css</url-pattern>
</filter-mapping>

注意:這里我們需要配置FORWARD和REQUEST,用于攔截forward請(qǐng)求。因?yàn)榇蠖鄶?shù)時(shí)候我們都是轉(zhuǎn)發(fā)過(guò)來(lái)的請(qǐng)求。

4.2 包裝response對(duì)象,緩存數(shù)據(jù)到內(nèi)存

CacheFiltet.java

public class CacheFilter implements Filter {
    //實(shí)際開(kāi)發(fā)中我們可以使用一些專(zhuān)業(yè)的緩存工具
    private Map<String, byte[]> map = new HashMap<String, byte[]>();
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        
        //1.得到用戶(hù)請(qǐng)求的uri
        String uri = request.getRequestURI();
        
        //2.看換出中有沒(méi)有uri對(duì)應(yīng)的數(shù)據(jù)
        byte b[] =  map.get(uri);
        
        
        //3.如果有,則直接拿緩存的數(shù)據(jù)送給瀏覽器,程序返回
        if(b != null){
            response.getOutputStream().write(b);
            return ;
        }
        
        //4.如果沒(méi)有,讓目標(biāo)資源執(zhí)行,并捕獲目標(biāo)資源的輸出
        BufferResponse1 myresResponse1 = new BufferResponse1(response);
        chain.doFilter(request, myresResponse1);
        byte out[] = myresResponse1.getBuffer();
        //5.把資源的數(shù)據(jù)以用戶(hù)請(qǐng)求的uri的關(guān)鍵字保存到緩存中
        map.put(uri, out);
        
        //6.把數(shù)據(jù)送給瀏覽器
        response.getOutputStream().write(out);
    }

    @Override
    public void destroy() {}

}

class BufferResponse1 extends HttpServletResponseWrapper{

    private HttpServletResponse response;
    private ByteArrayOutputStream bout = new ByteArrayOutputStream();//字節(jié)流
    private PrintWriter pw;
    
    public BufferResponse1(HttpServletResponse response) {
        super(response);
        this.response = response;
    }
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        
        return new MyServletOutputStream1(bout);
    }
    
    
    @Override
    public PrintWriter getWriter() throws IOException {
        
        pw = new PrintWriter(new OutputStreamWriter(bout,this.response.getCharacterEncoding()));
        return pw;
    }
    
    public byte[] getBuffer(){
        try{
            if(pw != null){
                //如果pw不為空,則我們需要關(guān)閉一下,讓其將數(shù)據(jù)從緩存寫(xiě)到底層流中去
                pw.close();
            }
            if(bout != null){
                bout.flush();
                return bout.toByteArray();
            }
            return null;
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }   
}
class MyServletOutputStream1 extends ServletOutputStream{
    
    private ByteArrayOutputStream bout ;
    public  MyServletOutputStream1(ByteArrayOutputStream bout) {
        this.bout = bout;
    }
    
    @Override
    public void write(int arg0) throws IOException {
        //其實(shí)write方法有三種重載形式,但是其內(nèi)部都是調(diào)用的這種形式,所以我們只需要重載這種形式即可
        this.bout.write(arg0);
        
    }
    @Override
    public boolean isReady() {
        return false;
    }

    @Override
    public void setWriteListener(WriteListener listener) {}
}

說(shuō)明:

  • 對(duì)于頁(yè)面中很少更新的數(shù)據(jù),例如商品分類(lèi),為避免每次都要從數(shù)據(jù)庫(kù)查詢(xún)分類(lèi)數(shù)據(jù),因此可以把分類(lèi)數(shù)據(jù)緩存在內(nèi)存或文件中,以此來(lái)減輕數(shù)據(jù)庫(kù)壓力,提高系統(tǒng)響應(yīng)速度。相關(guān)書(shū)名直接看程序中的注釋即可,這里不再細(xì)說(shuō)。

五、動(dòng)態(tài)代理

  • 在java里,每個(gè)對(duì)象都有一個(gè)類(lèi)與之對(duì)應(yīng)。

  • 現(xiàn)在要生成某一個(gè)對(duì)象的代理對(duì)象,這個(gè)代理對(duì)象也要通過(guò)一個(gè)類(lèi)來(lái)生成,所以首先要編寫(xiě)用于生成代理對(duì)象的類(lèi)。

  • 如何編寫(xiě)生成代理對(duì)象的類(lèi),兩個(gè)要素:

    • 代理誰(shuí)
    • 如何生成代理對(duì)象
  • 代理誰(shuí)?
    設(shè)計(jì)一個(gè)類(lèi)變量,以及一個(gè)構(gòu)造函數(shù),記住代理類(lèi)代理哪個(gè)對(duì)象。

  • 如何生成代理對(duì)象?
    設(shè)計(jì)一個(gè)方法生成代理對(duì)象(在方法內(nèi)編寫(xiě)代碼生成代理對(duì)象是此處編程的難點(diǎn))

  • java提供了一個(gè)Proxy類(lèi),調(diào)用它的newInstance方法可以生成某個(gè)對(duì)象的代理對(duì)象,使用該方法生成代理對(duì)象時(shí),需要三個(gè)參數(shù):

    • 1.生成代理對(duì)象使用哪個(gè)類(lèi)裝載器
    • 2.生成哪個(gè)對(duì)象的代理對(duì)象,通過(guò)接口指定
    • 3.生成的代理對(duì)象的方法里干什么事,由開(kāi)發(fā)人員編寫(xiě)handler接口的實(shí)現(xiàn)來(lái)指定。
  • 初學(xué)者必須理解(記住)

    • Proxy類(lèi)負(fù)責(zé)創(chuàng)建代理對(duì)象時(shí),如果指定了handler(處理器),那么不管用戶(hù)調(diào)用代理對(duì)象的什么方法,該方法都是調(diào)用處理器的invoke方法。
    • 由于invoke方法被調(diào)用需要三個(gè)參數(shù):代理對(duì)象、方法、方法的參數(shù),因此不管代理對(duì)象哪個(gè)方法調(diào)用處理器的invoke方法,都必須把自己所在的對(duì)象、自己(調(diào)用invoke方法的方法)、方法的參數(shù)傳遞進(jìn)來(lái)。

六、動(dòng)態(tài)代理應(yīng)用

  • 在動(dòng)態(tài)代理技術(shù)里,由于不管用戶(hù)調(diào)用代理對(duì)象的什么方法,都是調(diào)用開(kāi)發(fā)人員編寫(xiě)的處理器的invoke方法(這相當(dāng)于invoke方法攔截到了代理對(duì)象的方法調(diào)用)。

  • 并且,開(kāi)發(fā)人員通過(guò)invoke方法的參數(shù),才可以在攔截的同時(shí),知道用戶(hù)調(diào)用的是什么方法,因此利用這兩個(gè)特征,就可以實(shí)現(xiàn)一些特殊需求。例如:攔截用戶(hù)的訪(fǎng)問(wèn)請(qǐng)求,以檢查用戶(hù)是否有訪(fǎng)問(wèn)權(quán)限、動(dòng)態(tài)為某個(gè)對(duì)象添加額外的功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容