过滤器的理解

过滤器的理解

什么是过滤器?

我们很容易发现,过滤器可以比喻成一张滤网。我们想想现实中的滤网可以做什么:在泡茶的时候,过滤掉茶叶。那滤网是怎么过滤茶叶的呢?规定大小的网孔,只要网孔比茶叶小,就可以实现过滤了!

引申在Web容器中,过滤器可以做:过滤一些敏感的字符串【规定不能出现敏感字符串】、避免中文乱码【规定Web资源都使用UTF-8编码】、权限验证等等等,过滤器的作用非常大,只要发挥想象就可以有意想不到的效果

过滤器的知识点

学某项技术之前,首先要知道它能干什么,学了这项技术有什么好处,再细学

知道了什么是过滤器以后,其实我们学的东西就不是很多了,感觉花半天就能学完了。

首先,我们来认识一下Filter接口和相对应的doFilter()方法以及它的参数。

学过我之前的「Servlet」教程,对doFilter()里边的ServletRequestServletResponse应该就很了解了,我这里也不赘述了。唯一可能让人难以理解的就是FilterChain这个接口。

FilterChain接口里边其实也是一个doFilter方法。

我们可以这样理解:过滤器不单单只有一个,那么我们怎么管理这些过滤器呢?在Java中就使用了链式结构把所有的过滤器都放在FilterChain里边,如果符合条件,就执行下一个过滤器(如果没有过滤器了,就执行目标资源)

上面的话好像有点拗口,我们可以想象生活的例子:现在我想在茶杯上能过滤出石头和茶叶出来。石头在一层,茶叶在一层。所以茶杯的过滤装置应该有两层滤网。这个过滤装置就是FilterChain,过滤石头的滤网和过滤茶叶的滤网就是Filter。在石头滤网中,茶叶是属于下一层的,就把茶叶放行,让茶叶的滤网过滤茶叶。过滤完茶叶了,剩下的就是茶

对上面的API了解完了以后,我们试着自己写一个过滤器(实际上就是实现Filter接口,重写doFilter()方法),然后以注解/xml配置的方式来部署自己的过滤器。

随后看一下FilterChain的执行顺序是不是自己配置的那样,再写几个常见的过滤器应用就好了,比如说「禁止浏览器缓存」「实现自动登录」「编码过滤器」「敏感词过滤器」「压缩资源过滤器」「HTML转义过滤器」「缓存数据」…

img

工作中用「过滤器」多吗?

三歪在工作时间不长哈,接触了好多些系统,由我们自己去写「过滤器」的场景还是不多的。但我觉得有一点可以好好学学,就是「责任链模式」。

之前为啥我写了一篇「责任链模式」,其实就是这个设计模式在系统中用得挺多的,号称能搞掂if else

过滤器其实也是责任链模式的一种实现,FilterChain层层往下执行,直到最后没有过滤器,就到了「目标资源」

什么是监听器?

监听器就是一个实现特定接口的普通Java程序,这个程序专门用于监听一个Java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。

上面这句话应该也很好理解,比如说我有一个SanWai对象,里边有一个eat()方法。每当SanWai.eat()的时候,我的监听器可以监听到SanWai.eat()被调用了,于是我们就可以搞一波逻辑,做别的事了。

  • 比如说,三歪女朋友发现三歪要吃饭了,于是打电话让三歪少吃点。

回到Servlet层面上,我们更多的监听的是**「Session」「Request」「ServletContext」**这几个对象的创建/销毁/属性内的变化。

针对监听上面的几个对象,我们可以做出一些小例子,比如说「统计网站的在线人数」「自动踢人」「定时清除Session的值」

监听器在工作中用得多吗?

监听器在写业务代码的时候,同样也用得不多,我几乎没怎么写过监听器的代码。

但是理解监听器这个概念我觉得还是很有必要的。以我的理解,大概可以认为「A发生了变化,B需要依赖A发生的变化做出处理」,这就是监听器。

有人认为,这不就是「事件驱动」吗?我觉得也可以那样理解。

监听器和过滤器再总结

监听器和过滤器在工作中可能让我们自己「手写」的概率不是很大,但我觉得这两个技术还是需要了解的。如果你了解过Struts2,你就会发现Struts2就是用的过滤器来实现很多的功能。监听器在Spring源码里边也有很多的实现,我觉得都可以看看。

过滤器和监听器还是需要理解它的思想,这块对我们学习Spring也是很有帮助的。

为什么需要用到过滤器

直接举例子来说明吧:

过滤器解决中文乱码问题

  • 如果我没有用到过滤器:浏览器通过http请求发送数据给Servlet,如果存在中文,就必须指定编码,否则就会乱码!
  • jsp页面提交中文数据给Servlet处理
  • Servlet没有指定编码的情况下,获取得到的是乱码

img

Servlet中如何解决中文乱码问题,我的其他博文中有写(参考Servlet Request对象的文章)

也就是说:如果我每次接受客户端带过来的中文数据,在Serlvet中都要设定编码。这样代码的重复率太高了!!!!

有过滤器就可以解决中文乱码问题

有过滤器的情况就不一样了:只要我在过滤器中指定了编码,可以使全站的Web资源都是使用该编码,并且重用性是非常理想的!

过滤器 API

只要Java类实现了Filter接口就可以称为过滤器!Filter接口的方法也十分简单:

其中init()和destory()方法就不用多说了,他俩跟Servlet是一样的。只有在Web服务器加载和销毁的时候被执行,只会被执行一次!

值得注意的是doFilter()方法,**它有三个参数(ServletRequest,ServletResponse,FilterChain),**从前两个参数我们可以发现:过滤器可以完成任何协议的过滤操作

那FilterChain是什么东西呢?我们看看:

FilterChain是一个接口,里面又定义了doFilter()方法。这究竟是怎么回事啊??????

我们可以这样理解:过滤器不单单只有一个,那么我们怎么管理这些过滤器呢?在Java中就使用了链式结构把所有的过滤器都放在FilterChain里边,如果符合条件,就执行下一个过滤器(如果没有过滤器了,就执行目标资源)

上面的话好像有点拗口,我们可以想象生活的例子:现在我想在茶杯上能过滤出石头和茶叶出来。石头在一层,茶叶在一层。所以茶杯的过滤装置应该有两层滤网。这个过滤装置就是FilterChain,过滤石头的滤网和过滤茶叶的滤网就是Filter。在石头滤网中,茶叶是属于下一层的,就把茶叶放行,让茶叶的滤网过滤茶叶。过滤完茶叶了,剩下的就是茶(茶就可以比喻成我们的目标资源)

快速入门

写一个简单的过滤器

  • 实现Filter接口的Java类就被称作为过滤器
public class FilterDemo1 implements Filter {
    public void destroy() {
    }

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

        //执行这一句,说明放行(让下一个过滤器执行,如果没有过滤器了,就执行执行目标资源)
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

filter部署

过滤器和Servlet是一样的,需要部署到Web服务器上的。

第一种方式:在web.xml文件中配置

filter

<filter>用于注册过滤器

<filter>
         <filter-name>FilterDemo1</filter-name>
         <filter-class>FilterDemo1</filter-class>
         <init-param>
         <param-name>word_file</param-name> 
         <param-value>/WEB-INF/word.txt</param-value>
         </init-param>
</filter>

<filter-name>用于为过滤器指定一个名字,该元素的内容不能为空。
<filter-class>元素用于指定过滤器的完整的限定类名
<init-param>元素用于为过滤器指定初始化参数,它的子元素指定参数的名字,<param-value>指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数

filter-mapping

<filter-mapping>元素用于设置一个Filter 所负责拦截的资源

一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径

<filter-mapping>
    <filter-name>FilterDemo1</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-name>子元素用于设置filter的注册名称。该值必须是在元素中声明过的过滤器的名字
<url-pattern>设置 filter 所拦截的请求路径(过滤器关联的URL样式)
<servlet-name>指定过滤器所拦截的Servlet名称
<dispatcher>指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。

dispatcher

子元素可以设置的值及其意义:

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

第二种方式:通过注解配置

@WebFilter(filterName = "FilterDemo1",urlPatterns = "/*")

上面的配置是“/*”,所有的Web资源都需要途径过滤器

如果想要部分的Web资源进行过滤器过滤则需要指定Web资源的名称即可!

上面已经说过了,过滤器的doFilter()方法是极其重要的,FilterChain接口是代表着所有的Filter,FilterChain中的doFilter()方法决定着是否放行下一个过滤器执行(如果没有过滤器了,就执行目标资源)

测试一

  • 首先在过滤器的doFilter()中输出一句话,并且调用chain对象的doFilter()方法
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

    System.out.println("我是过滤器1");

    //执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
    chain.doFilter(req, resp);
}
  • 我们来访问一下test.jsp页面:

img

我们发现test.jsp(我们的目标资源)成功访问到了,并且在服务器上也打印了字符串!

测试二

我们来试试把chain.doFilter(req, resp);这段代码注释了看看!

img

测试三

直接看下面的代码。我们已经知道了”准备放行“会被打印在控制台上和test.jsp页面也能被访问得到,但“放行完成“会不会打印在控制台上呢?

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

    System.out.println("准备放行");

    //执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
    chain.doFilter(req, resp);

    System.out.println("放行完成");
}

答案也非常简单,肯定会打印在控制台上的。我们来看看:

img

注意,它的完整流程顺序是这样的:客户端发送http请求到Web服务器上,Web服务器执行过滤器,执行到”准备放行“时,就把字符串输出到控制台上,接着执行doFilter()方法,Web服务器发现没有过滤器了,就执行目标资源(也就是test.jsp)。目标资源执行完后,回到过滤器上,继续执行代码,然后输出”放行完成“

测试四

我们再多加一个过滤器,看看执行顺序。

  • 过滤器1
System.out.println("过滤器1开始执行");

//执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
chain.doFilter(req, resp);

System.out.println("过滤器1开始完毕");
  • 过滤器2
System.out.println("过滤器2开始执行");
chain.doFilter(req, resp);
System.out.println("过滤器2开始完毕");
  • Servlet
System.out.println("我是Servlet1");

当我们访问Servlet1的时候,看看控制台会出现什么:

执行顺序是这样的:先执行FilterDemo1,放行,执行FilterDemo2,放行,执行Servlet1,Servlet1执行完回到FilterDemo2上,FilterDemo2执行完毕后,回到FilterDemo1上

注意:过滤器之间的执行顺序看在web.xml文件中mapping的先后顺序的,如果放在前面就先执行,放在后面就后执行!如果是通过注解的方式配置,就比较urlPatterns的字符串优先级

Filter简单应用

  • filter的三种典型应用:
  • 1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
  • 2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
  • 3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能

禁止浏览器缓存所有动态页面

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

    //让Web资源不缓存,很简单,设置http中response的请求头即可了!

    //我们使用的是http协议,ServletResponse并没有能够设置请求头的方法,所以要强转成HttpServletRequest

    //一般我们写Filter都会把他俩强转成Http类型的
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;

    response.setDateHeader("Expires", -1);
    response.setHeader("Cache-Control", "no-cache");
    response.setHeader("Pragma", "no-cache");

    //放行目标资源的response已经设置成不缓存的了
    chain.doFilter(request, response);
}
  • 没有过滤之前,响应头是这样的:
  • 过滤之后,响应头是这样的:

实现自动登陆

开发实体、集合模拟数据库、Dao

  • 实体:
private String username ;
private String password;


public User() {
}

public User(String username, String password) {
    this.username = username;
    this.password = password;
}

//各种setter和getter
  • 集合模拟数据库
public class UserDB {

    private static List<User> users = new ArrayList<>();



    static {
        users.add(new User("aaa", "123"));
        users.add(new User("bbb", "123"));
        users.add(new User("ccc", "123"));
    }

    public static List<User> getUsers() {
        return users;
    }

    public static void setUsers(List<User> users) {
        UserDB.users = users;
    }
}
  • 开发dao
public User find(String username, String password) {

    List<User> userList = UserDB.getUsers();

    //遍历List集合,看看有没有对应的username和password
    for (User user : userList) {
        if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
            return user;
        }
    }
    return null;
}

登陆界面

<form action="${pageContext.request.contextPath}/LoginServlet">

    用户名<input type="text" name="username">
    <br>
    密码<input type="password" name="password">
    <br>

    <input type="radio" name="time" value="10">10分钟
    <input type="radio" name="time" value="30">30分钟
    <input type="radio" name="time" value="60">1小时
    <br>

    <input type="submit" value="登陆">

</form>

处理登陆的Servlet

//得到客户端发送过来的数据
String username = request.getParameter("username");
String password = request.getParameter("password");

UserDao userDao = new UserDao();
User user = userDao.find(username, password);

if (user == null) {
    request.setAttribute("message", "用户名或密码是错的!");
    request.getRequestDispatcher("/message.jsp").forward(request, response);
}

//如果不是为空,那么在session中保存一个属性
request.getSession().setAttribute("user", user);
request.setAttribute("message", "恭喜你,已经登陆了!");

//如果想要用户关闭了浏览器,还能登陆,就必须要用到Cookie技术了
Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + user.getPassword());

//设置Cookie的最大声明周期为用户指定的
cookie.setMaxAge(Integer.parseInt(request.getParameter("time")) * 60);

//把Cookie返回给浏览器
response.addCookie(cookie);

//跳转到提示页面
request.getRequestDispatcher("/message.jsp").forward(request, response);

过滤器

HttpServletResponse response = (HttpServletResponse) resp;
HttpServletRequest request = (HttpServletRequest) req;

//如果用户没有关闭浏览器,就不需要Cookie做拼接登陆了
if (request.getSession().getAttribute("user") != null) {
    chain.doFilter(request, response);
    return;
}

//用户关闭了浏览器,session的值就获取不到了。所以要通过Cookie来自动登陆
Cookie[] cookies = request.getCookies();
String value = null;
for (int i = 0; cookies != null && i < cookies.length; i++) {
    if (cookies[i].getName().equals("autoLogin")) {
        value = cookies[i].getValue();
    }
}

//得到Cookie的用户名和密码
if (value != null) {

    String username = value.split("\\.")[0];
    String password = value.split("\\.")[1];

    UserDao userDao = new UserDao();
    User user = userDao.find(username, password);

    if (user != null) {
        request.getSession().setAttribute("user", user);
    }
}

chain.doFilter(request, response);
  • 效果:

img

改良

我们直接把用户名和密码都放在了Cookie中,这是明文的。懂点编程的人就会知道你的账号了。

于是乎,我们要对密码进行加密!

Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + md5.md5(user.getPassword()));
  • 在过滤器中,加密后的密码就不是数据库中的密码的。所以,我们得在Dao添加一个功能【根据用户名,找到用户】
public User find(String username) {
    List<User> userList = UserDB.getUsers();

    //遍历List集合,看看有没有对应的username和password
    for (User user : userList) {
        if (user.getUsername().equals(username)) {
            return user;
        }
    }

    return null;
}
  • 在过滤器中,比较Cookie带过来的md5密码和在数据库中获得的密码(也经过md5)是否相同
//得到Cookie的用户名和密码
if (value != null) {

    String username = value.split("\\.")[0];
    String password = value.split("\\.")[1];

    //在Cookie拿到的密码是md5加密过的,不能直接与数据库中的密码比较
    UserDao userDao = new UserDao();
    User user = userDao.find(username);

    //通过用户名获得用户信息,得到用户的密码,用户的密码也md5一把

    String dbPassword = md5.md5(user.getPassword());
    //如果两个密码匹配了,就是正确的密码了
    if (password.equals(dbPassword)) {
        request.getSession().setAttribute("user", user);
    }

}

过滤器第二篇【编码、敏感词、压缩、转义过滤器】

编码过滤器

目的:解决全站的乱码问题

出现乱码的原因就是编码和解码不一致

1、服务器响应时出现乱码问题,

​ Eclipse采用的是UTF-8的编码,TomCat服务器默认的是ISO-8859-1编码,浏览器默认的是GBK编码,如下图所示:

​ 注意: 以字符为标准传输数据时,会产生数据丢失,数据丢失就会编码失败,进而乱码

参考文章:https://blog.csdn.net/yangjian1156/article/details/71473733

   解决方式   
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
		//设置服务器编码集
                response.setCharacterEncoding("UTF-8");
                //设置浏览器编码集              
               response.setHeader("content-type","text/html;charset=UTF-8");
		response.getWriter().print("你好!");//浏览器界面输出中文:你好!
	}
    注意:

开发过滤器

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

    //将request和response强转成http协议的
    HttpServletRequest httpServletRequest = (HttpServletRequest) req;
    HttpServletResponse httpServletResponse = (HttpServletResponse) resp;

    httpServletRequest.setCharacterEncoding("UTF-8");
    httpServletResponse.setCharacterEncoding("UTF-8");
    httpServletResponse.setContentType("text/html;charset=UTF-8");

    chain.doFilter(httpServletRequest, httpServletResponse);
}

第一次测试

Servlet1中向浏览器回应中文数据,没有出现乱码。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    response.getWriter().write("看完博客点赞!");

}

在Servlet获取浏览器以GET方式提交过来的中文是乱码的根本原因是:getParameter()方法是以ISO 8859-1的编码来获取浏览器传递过来的数据的,得到的是乱码

也就是说,sun公司为我们提供的request对象是不够用的,因为sun公司提供的request对象使用getParameter()获取get方式提交过来的数据是乱码,于是我们要增强request对象(使得getParameter()获取得到的是中文)!

增强request对象

包装设计模式的五个步骤:

  • **1、实现与被增强对象相同的接口 **
  • 2、定义一个变量记住被增强对象
  • 3、定义一个构造器,接收被增强对象
  • 4、覆盖需要增强的方法
  • 5、对于不想增强的方法,直接调用被增强对象(目标对象)的方法

sun公司也知道我们可能对request对象的方法不满意,于是提供了HttpServletRequestWrapper类给我们实现(如果实现HttpServletRequest接口的话,要实现太多的方法了!)

class MyRequest extends HttpServletRequestWrapper {

    private HttpServletRequest request;

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

    @Override
    public String getParameter(String name) {
        String value = this.request.getParameter(name);

        if (value == null) {
            return null;
        }

        //如果不是get方法的,直接返回就行了
        if (!this.request.getMethod().equalsIgnoreCase("get")) {
            return null;
        }

        try {

            //进来了就说明是get方法,把乱码的数据
            value = new String(value.getBytes("ISO8859-1"), this.request.getCharacterEncoding());
            return value ;

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();

            throw new RuntimeException("不支持该编码");
        }

    }
}

将被增强的request对象传递给目标资源,那么目标资源使用request调用getParameter()方法的时候,获取得到的就是中文数据,而不是乱码了!

//将request和response强转成http协议的
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse httpServletResponse = (HttpServletResponse) resp;

httpServletRequest.setCharacterEncoding("UTF-8");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html;charset=UTF-8");

MyRequest myRequest = new MyRequest(httpServletRequest);

//传递给目标资源的request是被增强后的。
chain.doFilter(myRequest, httpServletResponse);

第二次测试

  • 使用get方式传递中文数据给服务器
<form action="${pageContext.request.contextPath}/Servlet1" method="get">

    <input type="hidden" name="username" value="中国">


    <input type="submit" value="提交">
</form>

这里写图片描述

敏感词的过滤器

如果用户输入了敏感词(傻b、尼玛、操蛋等等不文明语言时),我们要将这些不文明用于屏蔽掉,替换成符号!

要实现这样的功能也很简单,用户输入的敏感词肯定是在getParameter()获取的,我们在getParameter()得到这些数据的时候,判断有没有敏感词汇,如果有就替换掉就好了!简单来说:也是要增强request对象

增强request对象

class MyDirtyRequest extends HttpServletRequestWrapper {

    HttpServletRequest request;

    //定义一堆敏感词汇
    private List<String> list = Arrays.asList("傻b", "尼玛", "操蛋");

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

    @Override
    public String getParameter(String name) {

        String value = this.request.getParameter(name);

        if (value == null) {
            return null;
        }

        //遍历list集合,看看获取得到的数据有没有敏感词汇
        for (String s : list) {

            if (s.equals(value)) {
                value = "*****";
            }
        }

        return value ;
    }
}

开发过滤器

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

    //将request和response强转成http协议的
    HttpServletRequest httpServletRequest = (HttpServletRequest) req;
    HttpServletResponse httpServletResponse = (HttpServletResponse) resp;

    MyDirtyRequest dirtyRequest = new MyDirtyRequest(httpServletRequest);

    //传送给目标资源的是被增强后的request对象
    chain.doFilter(dirtyRequest, httpServletResponse);
}

测试

这里写图片描述

按照过滤器的执行顺序:执行完目标资源,过滤器后面的代码还会执行。所以,我们在过滤器中可以获取执行完目标资源后的response对象!

我们知道sun公司提供的response对象调用write()方法,是直接把数据返回给浏览器的。我们要想实现压缩的功能,write()方法就不能直接把数据写到浏览器上!

这和上面是类似的,过滤器传递给目标资源的response对象就需要被我们增强,使得目标资源调用writer()方法的时候不把数据直接写到浏览器上

增强response对象

response对象可能会使用PrintWriter或者ServletOutputStream对象来调用writer()方法的,所以我们增强response对象的时候,需要把getOutputSteam和getWriter()重写

class MyResponse extends HttpServletResponseWrapper{

    HttpServletResponse response;
    public MyResponse(HttpServletResponse response) {
        super(response);
        this.response = response;
    }


    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return super.getOutputStream();
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return super.getWriter();
    }
}

接下来,ServletOutputSteam要调用writer()方法,使得它不会把数据写到浏览器上。这又要我们增强一遍了!

增强ServletOutputSteam

/*增强ServletOutputSteam,让writer方法不把数据直接返回给浏览器*/
class MyServletOutputStream extends ServletOutputStream{

    private ByteArrayOutputStream byteArrayOutputStream;

    public MyServletOutputStream(ByteArrayOutputStream byteArrayOutputStream) {
        this.byteArrayOutputStream = byteArrayOutputStream;
    }

    //当调用write()方法的时候,其实是把数据写byteArrayOutputSteam上
    @Override
    public void write(int b) throws IOException {
        this.byteArrayOutputStream.write(b);

    }
}

增强PrintWriter

PrintWriter对象就好办了,它本来就是一个包装类,看它的构造方法,我们直接可以把ByteArrayOutputSteam传递给PrintWriter上。

@Override
public PrintWriter getWriter() throws IOException {
    printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, this.response.getCharacterEncoding()));

    return printWriter;
}

获取缓存数据

我们把数据都写在了ByteArrayOutputSteam上了,应该提供方法给外界过去缓存中的数据!

public byte[] getBuffer() {

    try {

        //防止数据在缓存中,要刷新一下!
        if (printWriter != null) {
            printWriter.close();
        }
        if (byteArrayOutputStream != null) {
            byteArrayOutputStream.flush();
            return byteArrayOutputStream.toByteArray();
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

增强response的完整代码

class MyResponse extends HttpServletResponseWrapper{

    private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    private PrintWriter printWriter ;

    private HttpServletResponse response;
    public MyResponse(HttpServletResponse response) {
        super(response);
        this.response = response;
    }


    @Override
    public ServletOutputStream getOutputStream() throws IOException {

        //这个的ServletOutputSteam对象调用write()方法的时候,把数据是写在byteArrayOutputSteam上的
        return new MyServletOutputStream(byteArrayOutputStream);
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, this.response.getCharacterEncoding()));

        return printWriter;
    }

    public byte[] getBuffer() {

        try {

            //防止数据在缓存中,要刷新一下!
            if (printWriter != null) {
                printWriter.close();
            }
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.flush();
                return byteArrayOutputStream.toByteArray();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

过滤器

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


    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;
    MyResponse myResponse = new MyResponse(response);

    //把被增强的response对象传递进去,目标资源调用write()方法的时候就不会直接把数据写在浏览器上了
    chain.doFilter(request, myResponse);

    //得到目标资源想要返回给浏览器的数据
    byte[] bytes = myResponse.getBuffer();

    //输出原来的大小
    System.out.println("压缩前:"+bytes.length);


    //使用GZIP来压缩资源,再返回给浏览器
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
    gzipOutputStream.write(bytes);

    //得到压缩后的数据
    byte[] gzip = byteArrayOutputStream.toByteArray();

    System.out.println("压缩后:" + gzip.length);

    //还要设置头,告诉浏览器,这是压缩数据!
    response.setHeader("content-encoding", "gzip");
    response.setContentLength(gzip.length);
    response.getOutputStream().write(gzip);

}

测试

  • 在Servlet上输出一大段文字:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    response.getWriter().write("fdshfidsuhfidusfhuidsfhuidshdsuifhsd" +
            "uifhsduifffffdshfidsuhfidusfhuidsfhuidshdsuif" +
            "hsduifhsduifffffdshfidsuhfidusfhuidsfhuidshd" +
            "suifhsduifhsduifffffdshfidsuhfidusfhuidsfhuidsh" +
            "dsuifhsduifhsduifffffdshfidsuhfidusfhuidsfhuids" +
            "hdsuifhsduifhsduifffffdshfidsuhfidusfhuidsfhuid" +
            "shdsuifhsduifhsduiffdshfidsuhfidusfhuidsfhuids" +
            "hdsuifhsduifhsduifffffdshfidsuhfidusfhuidsfhui" +
            "dshdsuifhsduifhsduifffffdshfidsuhfidusfhuidsfh" +
            "uidshdsuifhsduifhsduifffffdshfidsuhfidusfhuids" +
            "fhuidshdsuifhsduifhsduifffffdshfidsuhfidusfhuid" +
            "sfhuidshdsuifhsduifhsduifffffdshfidsuhfidusfhui" +
            "dsfhuidshdsuifhsduifhsduifffffdshfidsuhfidusfh" +
            "uidsfhuidshdsuifhsduifhsduifffffdshfidsuhfidusf" +
            "huidsfhuidshdsuifhsduifhsduifffffdshfidsuhfidus" +
            "fhuidsfhuidshdsuifhsduifhsduifffffdshfidsuhfid" +
            "usfhuidsfhuidshdsuifhsduifhsduifffffdshfidsuhf" +
            "idusfhuidsfhuidshdsuifhsduifhsd" +
            "uifffffdshfidsuhfidusfhuidsfhuidshdsuifhsduifhsduifffffff");

}
  • 效果:

HTML转义过滤器

只要把getParameter()获取得到的数据转义一遍,就可以完成功能了。

增强request

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);
        return this.Filter(value);

    }

    public String Filter(String message) {
        if (message == null)
            return (null);

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

    }

}

过滤器

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



    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;
    MyHtmlRequest myHtmlRequest = new MyHtmlRequest(request);

    //传入的是被增强的request!
    chain.doFilter(myHtmlRequest, response);

}

测试

jsp代码:

<form action="${pageContext.request.contextPath}/Servlet1" method="post">


    <input type="hidden" name="username" value="<h1>你好i好<h1>">

    <input type="submit" value="提交">
</form>

Servlet代码:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    String value = request.getParameter("username");
    response.getWriter().write(value);

}

缓存数据到内存中

在前面我们已经做过了,让浏览器不缓存数据【验证码的图片是不应该缓存的】。

现在我们要做的是:缓存数据到内存中【如果某个资源重复使用,不轻易变化,应该缓存到内存中】

这个和压缩数据的Filter非常类似的,因为让数据不直接输出给浏览器,把数据用一个容器(ByteArrayOutputSteam)存起来。如果已经有缓存了,就取缓存的。没有缓存就执行目标资源!

增强response对象

class MyResponse extends HttpServletResponseWrapper {

    private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    private PrintWriter printWriter ;

    private HttpServletResponse response;
    public MyResponse(HttpServletResponse response) {
        super(response);
        this.response = response;
    }


    @Override
    public ServletOutputStream getOutputStream() throws IOException {

        //这个的ServletOutputSteam对象调用write()方法的时候,把数据是写在byteArrayOutputSteam上的
        return new MyServletOutputStream(byteArrayOutputStream);
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, this.response.getCharacterEncoding()));

        return printWriter;
    }

    public byte[] getBuffer() {

        try {

            //防止数据在缓存中,要刷新一下!
            if (printWriter != null) {
                printWriter.close();
            }
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.flush();
                return byteArrayOutputStream.toByteArray();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}


//增强ServletOutputSteam,让writer方法不把数据直接返回给浏览器

class MyServletOutputStream extends ServletOutputStream {

    private ByteArrayOutputStream byteArrayOutputStream;

    public MyServletOutputStream(ByteArrayOutputStream byteArrayOutputStream) {
        this.byteArrayOutputStream = byteArrayOutputStream;
    }

    //当调用write()方法的时候,其实是把数据写byteArrayOutputSteam上
    @Override
    public void write(int b) throws IOException {
        this.byteArrayOutputStream.write(b);

    }
}

过滤器

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

    //定义一个Map集合,key为页面的地址,value为内存的缓存
    Map<String, byte[]> map = new HashMap<>();

    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;

    //得到客户端想要请求的资源
    String uri = request.getRequestURI();
    byte[] bytes = map.get(uri);

    //如果有缓存,直接返回给浏览器就行了,就不用执行目标资源了
    if (bytes != null) {
        response.getOutputStream().write(bytes);
        return ;
    }

    //如果没有缓存,就让目标执行
    MyResponse myResponse = new MyResponse(response);
    chain.doFilter(request, myResponse);

    //得到目标资源想要发送给浏览器的数据
    byte[] b = myResponse.getBuffer();

    //把数据存到集合中
    map.put(uri, b);

    //把数据返回给浏览器
    response.getOutputStream().write(b);


}

测试

尽管是刷新,获取得到的也是从缓存拿到的数据!

这里写图片描述

Stream = byteArrayOutputStream;
}

//当调用write()方法的时候,其实是把数据写byteArrayOutputSteam上
@Override
public void write(int b) throws IOException {
    this.byteArrayOutputStream.write(b);

}

}


## 过滤器

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

//定义一个Map集合,key为页面的地址,value为内存的缓存
Map<String, byte[]> map = new HashMap<>();

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;

//得到客户端想要请求的资源
String uri = request.getRequestURI();
byte[] bytes = map.get(uri);

//如果有缓存,直接返回给浏览器就行了,就不用执行目标资源了
if (bytes != null) {
    response.getOutputStream().write(bytes);
    return ;
}

//如果没有缓存,就让目标执行
MyResponse myResponse = new MyResponse(response);
chain.doFilter(request, myResponse);

//得到目标资源想要发送给浏览器的数据
byte[] b = myResponse.getBuffer();

//把数据存到集合中
map.put(uri, b);

//把数据返回给浏览器
response.getOutputStream().write(b);

}


## 测试

**尽管是刷新,获取得到的也是从缓存拿到的数据!**

[外链图片转存中...(img-zEp9rvW9-1607426400430)]

转自:https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247487054&idx=1&sn=25f92798050d092027931e2ae0379e90&chksm=ebd74f4fdca0c6595bc795fd00354cf683d4593550cdd38ba7103893dd622606fc8f55fe6631&token=306734573&lang=zh_CN#rd
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值