Filter和Listener

过滤器

Servlet过滤器与Servlet十分相似,但它具有拦截客户端(浏览器)请求的功能,Servlet过滤器可以改变请求中的内容,来满足实际开发中的需要。对于程序开发人员而言,过滤器实质上就是在Web应用服务器上的一个Web应用组件,用于拦截客户端(浏览器)与目标资源的请求,并对这些请求进行一定过滤处理再发送给目标资源。

在Web容器中部署了过滤器以后,不仅客户端发送的请求会经过过滤器的处理,而且请求在发送到目标资源处理以后,请求的回应信息也同样要经过过滤器。

image

如果一个Web应用中使用一个过滤器不能解决实际中的业务需要,那么可以部署多个过滤器对业务请求进行多次处理,这样做就组成了一个过滤器链。Web容器在处理过滤器链时,将按过滤器的先后顺序对请求进行处理。

image

过滤器的使用

使用过滤器需要实现Filter接口。除这个接口外,与过滤器相关的对象还有FilterConfig对象与FilterChain对象,这两个对象也同样是接口对象,分别为过滤器的配置对象与过滤器的传递工具。

步骤

  1. 实现Filter接口,重写doFilter()方法
  2. 在doFilter方法中使用request和response对象完成具体操作
  3. 最后使用FilterChain对象的doFilter()方法将请求或响应传递给下一个过滤器。如果此过滤器已经是过滤器链中的最后一个过滤器,请求将传送给目标资源。
  4. 在xml文件中对过滤器进行配置,说明拦截URL的范围

过滤器的配置

  1. 单个过滤器使用注解配置比较简单:
    完整版:@WebFilter(filterName = "MyFirstFilter",urlPatterns = "/*")
    简化版:@webFilter("/*")

  2. 过滤器链需要配置web.xml

    <!-- 指定具体的过滤器 -->
        <filter>
            <filter-name>firstFilter</filter-name>
            <filter-class>com.example.filter.FirstFilter</filter-class>
        </filter>
        <!-- 指定过滤器拦截的URL的范围 -->
        <filter-mapping>
            <filter-name>firstFilter</filter-name>
            <url-pattern>/*</url-pattern><!-- 这样设置的话,所有的包括静态页面都会被拦截 -->
        </filter-mapping>
    

过滤器的生命周期

  1. 初始化 - Filter.init()
  2. 提供服务 - Filter.doFilter()
  3. 销毁 - Filter.destroy()

过滤器在服务器启动的时候被初始化,在服务器关闭的时候被销毁。过滤器对象是单例的,通过多线程的方式提供多用户环境。

字符集过滤器

通过过滤器可以统一解决中文乱码问题。

package com.example.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
public class CharsetFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //1.类型转换
        HttpServletRequest request =  (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        //2.设置字符集
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        //3.放行
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

过滤器参数化

上面的字符集过滤器需要设置字符集为 utf-8 ,我们把编码写在了代码中,修改起来很不方便。JavaEE 为了增加灵活性,允许将配置信息放在web.xml中,在 <init-param>中设置参数。

web.xml:

<filter>
        <filter-name>CharsetFilter</filter-name>
        <filter-class>com.example.filter.CharsetFilter</filter-class>
        <!-- 初始化参数配置在下面,可以通过FilterConfig对象读取-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharsetFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Filter:

package com.example.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter("/*")
public class CharsetFilter implements Filter {

    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        encoding = filterConfig.getInitParameter("encoding"); //获取初始化参数
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //1.类型转换
        HttpServletRequest request =  (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        //2.设置字符集
        request.setCharacterEncoding(encoding);
        response.setContentType("text/html;charset="+encoding);

        //3.放行
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

url-pattern设置过滤范围

/index.jsp - 资源精确匹配

/servlet/* - 前缀模糊匹配

*.jsp - 后者模糊匹配

一个过滤器可以设置多个 url-pattern,来匹配多种资源

过滤器链

过滤器的执行顺序以 <filter-mapping>的编写顺序为准。

如果是通过注解配置,那么执行顺序以类名的字符串排序来执行。可以起名像Filter1、Filter2这样的,就会按照顺序执行。

设备适配过滤器

案例需求:为电脑、手机编写不同的网页,通过电脑和手机访问可以分别显示。

步骤:

  1. 编写不同的页面,一个位于 desktop 目录下,另一个位于 mobile 目录下,文件名都是 index.html
  2. 获取user-agent并判断
  3. 如果是电脑端,在请求uri中添加/desktop
  4. 如果是手机端,在请求uri中添加/mobile

Filter:

package com.example.filter;

import com.sun.tools.javac.jvm.Code;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;

@WebFilter("/*")
public class DeviceAdapterFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)servletRequest;
        HttpServletResponse resp = (HttpServletResponse)servletResponse;

        //获取请求uri。uri很简洁,没有前面的localhost:8080/虚拟路径,只有/index.html
        String uri = req.getRequestURI();
        //如果uri包含了desktop或者mobile就不用处理,直接放行
        if(uri.startsWith("/desktop")||uri.startsWith("/mobile")){
            filterChain.doFilter(servletRequest,servletResponse);
        }else{
            //读取user-agent,全部转为小写
            String ua = req.getHeader("user-agent").toLowerCase();
     
            if(ua.contains("android")||ua.contains("iphone")){
                //生成新的uri
                String targetURI = "/mobile"+uri;
                System.out.println("移动端设备正在访问,重新跳转uri:"+targetURI);
                //请求重定向
                resp.sendRedirect(targetURI);
            }else{
                String targetURI = "/desktop"+uri;
                System.out.println("PC端设备正在访问,重新跳转uri:"+targetURI);
                resp.sendRedirect(targetURI);
            }

        }
    }
}

监听器

可以对web应用对象的行为进行监控。一旦监听到了对象状态发生变化,可以自动触发操作。有三种监听对象:ServletContext、HttpSession、ServletRequest

使用

  1. 实现不同对象的Listener接口
  2. 在web.xml配置 <listener>
package com.example.listener; 

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

@WebListener
public class FirstListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        /* This method is called when the servlet context is initialized(when the Web application is deployed). */
        System.out.println("ServletContext被初始化了");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        /* This method is called when the servlet Context is undeployed or Application Server shuts down. */
        System.out.println("ServletContext被销毁了");
    }
}

配置web.xml

<listener>
        <listener-class>com.example.listener.FirstListener</listener-class>
    </listener>

或者使用注解配置:@WebListener

监听接口

属性监听:

  1. ServletContextAttributeListener
  2. HttpSessionAttributeListener
  3. ServletRequestAttributeListener

请求流量统计

监听器最适合的应用场景是统计网站的访问了。

步骤:

  1. 在ServletContext 中保存两个属性,记录时间和访问量。
  2. 通过ServletRequest监听器监听请求,每请求一次就记录下当前时间并把访问量增加一次
  3. 编写Servlet动态显示网站访问量,使用了JS的echarts库。

监听器代码:

package com.example.listener; 

import javax.servlet.*;
import javax.servlet.annotation.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@WebListener
public class WebStatisticListener implements ServletContextListener, ServletRequestListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        /* This method is called when the servlet context is initialized(when the Web application is deployed). */
        //在ServletContext中记录访问量和访问时间,为了连续的展示,我们选择了列表
        List<String> timeList = new ArrayList<>();
        List<Integer> valueList = new ArrayList<>();
        sce.getServletContext().setAttribute("timeList",timeList);
        sce.getServletContext().setAttribute("valueList",valueList);
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        //当监听到了一次请求,就记录下访问时间并把访问量加一

        //1.获取ServletContext中保存的访问时间和访问量列表
        List<String> timeList =(List)sre.getServletContext().getAttribute("timeList");
        List<Integer> valueList = (List)sre.getServletContext().getAttribute("valueList");

        //2.获取当前时间并格式化
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
        String time = sdf.format(date);

        //3.如果时间列表里有当前时间
        if(timeList.contains(time)){
            //4.1 就把当前时间对应的访问量加一
            int index = timeList.indexOf(time);
            valueList.set(index,valueList.get(index)+1);
        }else{
            //4.2 如果时间列表里没有当前时间,把当前时间加入时间列表,同时给对应的访问量加一。
            timeList.add(time);
            valueList.add(1);
        }

        //5.最后把修改后的时间列表和访问量列表重新设放回应用域
        sre.getServletContext().setAttribute("timeList",timeList);
        sre.getServletContext().setAttribute("valueList",valueList);

    }
}

显示Servlet代码:

package com.example.listener;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/ss")
public class StatisticServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //这个Servlet用来动态显示网站

        List<String> timeList =(List)getServletContext().getAttribute("timeList");
        List<Integer> valueList = (List)getServletContext().getAttribute("valueList");

        resp.getWriter().println("<h1>"+timeList+"</h1>");
        resp.getWriter().println("<h1>"+valueList+"</h1>");
    }
}

posted @ 2021-08-29 16:18  黄了的韭菜  阅读(42)  评论(0编辑  收藏  举报