27、Filter开发(3)(包装request和response)(JavaEE笔记)

主要内容:

  • filter的部署
  • 包装设计模式对request和response进行包装增强

一、Filter的部署-注册Filter

  • :指定过滤器的名字,内容不能为空。

  • : 指定过滤器的完整的限定类名

  • 指定初始化参数,其子元素指定参数的名字,指定参数的值。可以使用FilterConfig接口对象来访问初始化参数。

  • 设置一个Filter所负责拦截的资源。一个Filter拦截的资源可以通过两种方式来指定:Servlet名称和资源访问的请求路径。

    • 子元素用于设置Filter注册名称,和上面一样。
    • 设置Filter所拦截的请求路径;/表示拦截所有,.jsp表示拦截jsp文件。等等。
    • 指定过滤器所拦截的资源被Servlet容器调用的方式,可以是REQUEST,INCLUDE,FORWARD,ERROR之一(必须是大写),默认是REQUEST。用户可以设置多个子元素来指定Filter对资源的多种调用方式进行拦截。
  • 子元素可以设置的值及其意义

    • REQUEST:当用户直接访问页面时,web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include或forward方法访问时,那么该过滤器就不会被调用。
    • INCLUDE:如果目标资源是通过RequestDispatcher的include方法访问时,那么该过滤器将被调用,除此之外,该过滤器将不会被调用。
    • FORWARD:如果目标资源是通过RequestDispatcher的forward方法访问时,那么该过滤器将被调用,除此之外,该过滤器将不会被调用。
    • ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

示例:
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() {
    }
}

配置:


    FilterDemo4
    cn.itcast.web.filter.FilterDemo4
  
  
    FilterDemo4
    /*
    ERROR
  
  
    java.lang.ArithmeticException
    /2.jsp
  

1.jsp


    <%
        int x = 1/0;
     %>
  

2.jsp


error page

说明:

  • 1.本来我们是在1.jsp中配置errorPage="/2.jsp",但是这样配置不起作用。于是我们在web.xml中进行配置。试验时我们访问1.jsp,那么就会被过滤器拦截下来,不会到达2.jsp
  • 2.在以后的开发中我们经常要拦截forward转发的资源,注意在配置文件中进行配置,不然是不会起作用的。在上个例子中我们可以看到。

二、filter高级开发

由于开发人员在Filter中可以得到代表用户请求和响应的request、response对象,因此在编程中可以使用Decorator(装饰器)模式对这些对象进行包装,再把包装后的对象传给目标资源,从而实现一些特殊需求。

注意:有四种方式访问web资源,正常的通过浏览器直接访问(request方式)、forward方式、include方式和error方式。

2.1回顾包装开发模式

之前我们在笔记20中讲到过包装开发模式,这里再次回顾一下。使用包装设计模式对BufferedReader类进行包装增强。

BufferedReaderWrapper.java

package cn.itcast.demo;

import java.io.BufferedReader;
import java.io.IOException;
//使用包装设计模式对BufferedReader类进行包装增强
/*
 * 1.实现与被增强对象相同的接口,如果接口方法太多,也可以继承一个类
 * 2.定义一个变量记住被增强对象
 * 3.定义一个构造器,接收被增强对象
 * 4.覆盖需要增强的方法
 * 5.对于不想增强的方法,直接调用被增强对象的方法
 * 
 * */
import java.io.Reader;

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

    public BufferedReaderWrapper(BufferedReader br) {
        super(br);
        // 子类在使用构造函数的时候会调用父类的构造函数,但是
        // 这里不知道调用父类哪个构造函数,于是就调用无参构造
        // 函数,但是父类又没有无参构造函数,这样就会报错,所
        // 以这里我们要指定调用父类哪个构造函数
        this.br = br;
    }

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

测试: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();
    }
}

说明:BufferedReader类中的readLine方法可以读取一行文本,这里我们想让readLine读取一行文本时还在本行最前面加上行号,于是我们使用包装设计模式对此方法进行包装。注意:这里当此方法读到空行的时候返回的是"",而不是null。
记住:能用包装设计模式就不要用子类的方式

2.2Decorator(包装)设计模式

  • 当某个对象的方法不适应业务需求时,通常有两种方式可以对方法进行增强:

    • 编写子类,覆盖需要增强的方法
    • 使用Decorator设计模式对方法进行增强
    • 使用动态代理(这里先不讲)
  • 在实际开发中遇到需要增强对象的方法时,到底选择用哪种方式

    • 没有具体的定式,不过有一种情况下,必须使用Decorator设计模式,即被增强对象,开发人员只能得到它的对象,无法得到它的class文件。
    • 比如request、response对象,开发人员之所以在Servlet中能通过sun公司定义的HttpServletRequest\HttpServletResponse接口去操作这些对象,是因为tomcat服务器厂商编写了request、response接口的实现类。Web服务器在调用Servlet时,会用这些接口的实现类创建出对象,然后传递给Servlet程序。
    • 此种情况下,由于开发人员根本不知道服务器厂商编写的request、response接口的实现类是哪个,在程序中只能拿到其提供的对象,因此就只能采用Decorator设计模式对这些对象进行增强。
  • Decorator设计模式的实现
    1.首先看需要被增强对象继承了什么接口或父类,编写一个类也去继承这些接口或父类。
    2.在类中定义一个变量,变量类型即需要增强对象类型。
    3.在类中定义一个构造函数,接收需要增强的对象。
    4.覆盖需要增强的方法,编写增强的代码。
    使用此设计模式为BufferedReader类的readLine方法添加行号的功能。在上面的例子中我们可以看到。

三、对request对象的增强(工程day18_2

  • ServletAPI中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapperHttpServletRequestWrapper类实现了request接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的request对象的对应方法)以避免用户在对request对象进行增强时需要实现request接口中的所有方法(这样太麻烦)。
  • 使用Decorator模式包装request对象,完全解决get、post请求方式下的乱码问题(前面有过这样一个例子,但是那个例子中只能解决post方式下的乱码问题)。

3.1 包装request对象,解决乱码问题

过滤器: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方式的乱码问题
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        // 解决get方式的乱码问题
        MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(
                request);

        chain.doFilter(requestWrapper, response);
    }

    @Override
    public void destroy() {
    }
}

class MyCharacterEncodingRequest extends HttpServletRequestWrapper {
    // 这里我们使用一个变量记住Servlet传递的request对象。
    private HttpServletRequest request;

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

    @Override
    // 增强此方法,此方法得到表单提交的数据
    public String getParameter(String name) {
        String value = this.request.getParameter(name);
        if (value == null) {// 如果为空直接返回空即可
            return null;
        }
        if (!this.request.getMethod().equalsIgnoreCase("get")) {
            // 如果不是get方式则没必要转换
            return value;
        }
        try {
            /*
             * value = new
             * String(value.getBytes("ISO8859-1"),"UTF-8");//手工转换,不要写死
             */

            value = new String(value.getBytes("ISO-8859-1"),
                    this.request.getCharacterEncoding());// 和Request设置的编码一致
            return value;// 这里使用ISO-8859-1没有解决乱码,这里浏览器
            // 提交的本就是UTF-8编码,不需要转换
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

说明:由于使用request.setCharacterEncoding("UTF-8");方式对get方式提交的数据无效,所以之前那个过滤器只能解决post方式提交的数据乱码问题。既然这种方式不能解决get方式提交的数据的乱码问题,那么我们可以将HttpServletRequest包装之后再给用户使用。这里我们主要是对其方法getParameter进行增强。这样就解决了get方式提交的乱码问题。

3.2 包装request对象,实现html标签转义功能

E:\apache\apache-tomcat-8.0.28-src\webapps\examples\WEB-INF\classes\util\HTMLFilter.java提供相应的例子)
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对象,实现对脏话进行过滤

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 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对象

4.1 包装response对象,实现压缩响应

GzipFilter.java

package cn.itcast.web.filter;
//解决全站的压缩问题,对Response进行增强,但是这个过滤器只是解决压缩字符,需要把数据都写到内存中去,如果是下载一个大文件,内存可能会崩,这里我们可以在
//配置文件中设定只对字符压缩有效,这拦截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();//先获得数据
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        GZIPOutputStream gout = new GZIPOutputStream(bout);//进行压缩
        gout.write(out);
        gout.close();//一定要关闭,这样数据才会从缓存中写入到底层流中去
        
        byte gzip[] = bout.toByteArray();//从底层流中取得数据
        response.setHeader("content-encoding", "gzip");//这里需要告诉浏览器这是一个压缩数据
        response.setContentLength(gzip.length);
        response.getOutputStream().write(gzip);//写出到浏览器
        
    }

    @Override
    public void destroy() {}

}

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

*之后servlet在使用Response的时候其实是使用我们自己定义的Response,而调用的getOutputStream方法也是我们自己定义的,这个方法返回的是
*MyServletOutputStream,然后调用write方法也是调用我们自己定义的write方法,此方法是将数据写到ByteArrayOutputStream底层流中。
*
*/


class BufferResponse extends HttpServletResponseWrapper{

    private HttpServletResponse response;
    private ByteArrayOutputStream bout = new ByteArrayOutputStream();//字节流
    private PrintWriter pw;
    
    public BufferResponse(HttpServletResponse response) {
        super(response);
        this.response = response;
    }
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        //这里我们对此方法进行了增强,不管是图片还是文本等数据都会进行压缩,但是如果直接访问jsp却不会,因为jsp一般是调用getWriter
        //方法,所以这里我们需要对getWriter方法进行增强
        return new MyServletOutputStream(bout);
    }
    
    @Override
    public PrintWriter getWriter() throws IOException {
        
        /*return new PrintWriter(bout);//因为PrintWriter有接受一个底层流的构造函数,所以这里我们不需要重写一个,但是这个方法也是一个包装类
        //这个类当缓存没有写满的时候是不会讲数据写到底层流中去,所以这里我们需要强制关闭此类*/   
        //pw = new PrintWriter(bout);//jsp中的汉字是一个字符流,这里会将其先转换为字节流,查的码表是gb2312的码表,但是我们设置的码表是UTF-8
        //而PrintWriter有一个接受一个字符流的方法,而字符流就会有设置码表的方法,而OutputStreamWriter是字符流到字节流的一个转换流,里面就可以指定码表
        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) {}
}

说明:

  • 这个包装类的实现有点难理解,这里我们详细说明一下。这里是实现数据的压缩之后再输出给浏览器,而我们完全可以在servlet中先从缓存中拿到数据压缩之后再输出,但是那样的话每个需要压缩资源的servlet都需要编写重复的代码,所以这里我们使用过滤器进行简化。
  • 首先服务器将资源写给浏览器的时候(注意这里和request过滤器的方向是反的),会被这个浏览拦截到。拦截到之后我们对response进行增强。
  • 而一般会调用getOutputStream方法和getWriter方法向浏览器中写数据,于是这里我们对这两个方法进行增强。
  • 方法getOutputStream会返回一个ServletOutputStream流,我们需要增强,我们要让此方法返回一个我们自己定义的一个流,于是对此类也进行包装。
  • 方法getWriter中我们将自己定义的流传递给PrintWriter方法。
  • 我们将数据压缩之后存入到底层流中,之后用户在调用getOutputStream方法和getWriter时拿到的数据就是我们压缩之后的数据。
  • 其实整个过程就是当用户调用方法向浏览器输出数据的时候我们将response的相关方法进行增强(实现数据压缩)之后再去调用真正response的方法进行输出,这样就可以实现压缩。

配置:


    GzipFilter
    cn.itcast.web.filter.GzipFilter


    GzipFilter
    *.jsp
    FORWARD
    REQUEST


    GzipFilter
    *.js


    GzipFilter
    *.css

注意:这里我们需要配置FORWARD和REQUEST,用于拦截forward请求。因为大多数时候我们都是转发过来的请求。

4.2 包装response对象,缓存数据到内存

CacheFiltet.java

public class CacheFilter implements Filter {
    //实际开发中我们可以使用一些专业的缓存工具
    private Map map = new HashMap();
    
    @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.得到用户请求的uri
        String uri = request.getRequestURI();
        
        //2.看换出中有没有uri对应的数据
        byte b[] =  map.get(uri);
        
        
        //3.如果有,则直接拿缓存的数据送给浏览器,程序返回
        if(b != null){
            response.getOutputStream().write(b);
            return ;
        }
        
        //4.如果没有,让目标资源执行,并捕获目标资源的输出
        BufferResponse1 myresResponse1 = new BufferResponse1(response);
        chain.doFilter(request, myresResponse1);
        byte out[] = myresResponse1.getBuffer();
        //5.把资源的数据以用户请求的uri的关键字保存到缓存中
        map.put(uri, out);
        
        //6.把数据送给浏览器
        response.getOutputStream().write(out);
    }

    @Override
    public void destroy() {}

}

class BufferResponse1 extends HttpServletResponseWrapper{

    private HttpServletResponse response;
    private ByteArrayOutputStream bout = new ByteArrayOutputStream();//字节流
    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不为空,则我们需要关闭一下,让其将数据从缓存写到底层流中去
                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 {
        //其实write方法有三种重载形式,但是其内部都是调用的这种形式,所以我们只需要重载这种形式即可
        this.bout.write(arg0);
        
    }
    @Override
    public boolean isReady() {
        return false;
    }

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

说明:

  • 对于页面中很少更新的数据,例如商品分类,为避免每次都要从数据库查询分类数据,因此可以把分类数据缓存在内存或文件中,以此来减轻数据库压力,提高系统响应速度。相关书名直接看程序中的注释即可,这里不再细说。

五、动态代理

  • 在java里,每个对象都有一个类与之对应。

  • 现在要生成某一个对象的代理对象,这个代理对象也要通过一个类来生成,所以首先要编写用于生成代理对象的类。

  • 如何编写生成代理对象的类,两个要素:

    • 代理谁
    • 如何生成代理对象
  • 代理谁?
    设计一个类变量,以及一个构造函数,记住代理类代理哪个对象。

  • 如何生成代理对象?
    设计一个方法生成代理对象(在方法内编写代码生成代理对象是此处编程的难点)

  • java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:

    • 1.生成代理对象使用哪个类装载器
    • 2.生成哪个对象的代理对象,通过接口指定
    • 3.生成的代理对象的方法里干什么事,由开发人员编写handler接口的实现来指定。
  • 初学者必须理解(记住)

    • Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
    • 由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。

六、动态代理应用

  • 在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。

  • 并且,开发人员通过invoke方法的参数,才可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特征,就可以实现一些特殊需求。例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。

你可能感兴趣的:(27、Filter开发(3)(包装request和response)(JavaEE笔记))