JAVAWEB过滤器、拦截器的作用及使用

过滤器Filter

什么是过滤器

  • 过滤器是运行在服务端的程序
  • 过滤器是在达到目标资源前的预处理操作(servlet、jsp、html等)
  • 过滤器是在请求即将离开服务器之前的处理程序
  • 多个过滤器可以组合使用,形成一个过滤链

为什么要使用过滤器(过滤器所能解决的问题)

  • 为了解决大量重复代码的出现
  • 例如配置项目的编码
  • 例如登录的拦截(如果每个servlet中都写一个是否登录判断,代码量将非常多)

配置一个过滤器完成编码的过滤

编写一个EncodingFilter(名称自定义)

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

public class EncodingFilter implements Filter {
    private String charset;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 从配置中读取编码
        charset = filterConfig.getInitParameter("charset");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 将req和resp对象转换为带Http协议的
        HttpServletRequest req = (HttpServletRequest)servletRequest;
        HttpServletResponse resp = (HttpServletResponse)servletResponse;

        // 配置请求post编码过滤
        req.setCharacterEncoding(charset);

        // 配置默认相应类型及编码
        resp.setContentType("text/html; charset=" + charset);
        
        // 执行下一个过滤器或者目标资源,理解为放行
        filterChain.doFilter(req, resp);
    }

    @Override
    public void destroy() {
        System.out.println("EncodingFilter 过滤器已销毁");
    }
}

在web.xml中配置过滤器

    <filter>
        <filter-name>myEncodingFilter</filter-name>
        <filter-class>com.oa.filter.EncodingFilter</filter-class>
        <init-param>
            <param-name>charset</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>myEncodingFilter</filter-name>
        <!-- 意思是拦截/abc下的所有资源,可以为Servlet指定前缀(例如abc),因为编码过滤只需要涉及到Servlet -->
        <url-pattern>/abc/*</url-pattern>
    </filter-mapping>

配置一个测试的Servlet

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;

@WebServlet("/abc/encoding/demo")
public class EncodingServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 未配置过滤器前是null,配置后是 UTF-8
        System.out.println(req.getCharacterEncoding());
    }
}

配置项目的登录控制(如果未登录不让访问资源)

配置一个Filter并使用注解的方式注册Filter

import com.oa.entity.Employee;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

// 表示拦截所有/abc路径下的资源、以及所有的jsp和html
@WebFilter(urlPatterns = {"/abc/*","*.jsp","*.html"})
public class LoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 将req和resp对象转换为带Http协议的
        HttpServletRequest req = (HttpServletRequest)servletRequest;
        HttpServletResponse resp = (HttpServletResponse)servletResponse;

        // 判断当前请求的路径是否为登录页面或登录提交的Servlet
        String requestUri = req.getRequestURI();
        String loginPage = req.getContextPath() + "/login.jsp";
        String loginServlet = req.getContextPath() + "/sys/login";
        
        if(loginPage.equals(requestUri)|| loginServlet.equals(requestUri)){
            filterChain.doFilter(req, resp);
        }else {
            // 获取到Session对象,并通过session从session域中获取用户
            HttpSession session = req.getSession();
            Employee employee = (Employee)session.getAttribute("user");

            // 处理结果
            if(employee == null) {
                resp.sendRedirect(req.getContextPath() + "/login.jsp");
            }else {
                // 放行
                filterChain.doFilter(req, resp);
            }
        }
    }

    @Override
    public void destroy() {

    }
}

关于过滤器的几个小问题

  • 多个过滤器执行的顺序如何确定
    答:通过filter-mapping配置的顺序执行

  • 每个请求或响应都需要经过过滤器吗
    答:通过url-pattern决定

  • 请求和响应是否将过滤器代码从头到尾执行
    答:不是,会先执行预处理操作,然后放行到下一个资源或过滤器,最后再执行后处理操作

  • 在过滤器中是否可以跳转到任意资源
    答:可以,因为重定向可以写绝对路径

  • 重定向和转发是否经过过滤器
    答:重定向经过过滤器,而转发默认不经过过滤器
    可以通过配置过滤器的filter-mapping中的dispatcher属性来让转发也经过过滤器(不推荐,基本不会这么做)

    <filter-mapping>
        <filter-name>myEncodingFilter</filter-name>
        <url-pattern>/abc/*</url-pattern>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

Filter的执行顺序

  • Filter会按照配置的filter-mapping的先后顺序执行相同的过滤
  • Filter过滤链在执行时会先进后出(在执行到filterChain.doFilter(req, resp)时)
// 执行到该行时,会先执行完下一个过滤器的filterChain.doFilter(req, resp);后的语句,才会执行到该过滤器的System.out.println("我是一个过滤器");
filterChain.doFilter(req, resp);
System.out.println("我是一个过滤器");

监听器Listener

什么是监听器?监听器的作用是什么?

  • 什么是监听器
    监听器是以观察者设计模式实现的,相当于一个监视者,监视着你的动作并做出响应
  • 监听器的作用
    javaweb为我们提供的监听器一共有8个,可以分别用来监听request、session、application作用域的创建、销毁、内容的创建

javax中所提供的监听器(都是接口)

  • ServletRequestListener
    监听request请求的建立和销毁(2个方法)

  • ServletRequestAttributeListener
    监听Request的作用域中属性的存入、删除、替换(当request销毁时也会进入删除的方法)

  • ServletContextListener
    监听application的建立和销毁(2个方法)

  • ServletContextAttributeListener
    监听Application的作用域中属性的存入、删除、替换(当application销毁时也会进入删除的方法)

  • HttpSessionListener
    监听Session的建立和销毁

  • HttpSessionAttributeListener
    监听Session的作用域中属性的存入、删除、替换(当session销毁时也会进入删除的方法)

  • HttpSessionActivationListener(无需在web.xml中配置)
    监听Session作用域中监听了该对象的钝化和活化(序列化、反序列化)

  • HttpSessionBindingListener(无需在web.xml中配置)
    监听Session作用域中监听对象的绑定与解绑(存入session作用域、在session作用域中删除),这个跟HttpSessionAttributeListener很像,不过这个是针对单个对象的

使用监听器实现在线人数的统计

分析:

  • 每次用户登录成功后就有了一位在线人数(session存入了user一个对象)
    可以使用HttpSessionAttributeListener监听存入
  • 每当用户点击注销时(session销毁时)
    可以使用HttpSessionListener监听销毁
  • 在线人数存放在哪??
    在线人数存在Appliction作用域,也就是ServletContext对象中

编写统计在线人数的监听器

import javax.servlet.ServletContext;
import javax.servlet.http.*;

public class OnLineListener implements HttpSessionAttributeListener, HttpSessionListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        // 当前存入到session作用域中的key值
        String name = event.getName();
        // 判断是否是user
        if ("user".equals(name)) {
            // 获得容器对象
            ServletContext servletContext = event.getSession().getServletContext();
            // 获取在线统计人数
            Integer online = (Integer) servletContext.getAttribute("online");
            if (online == null) {
                online = 1;
            } else {
                online ++;
            }
            servletContext.setAttribute("online", online);
        }
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {

    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {

    }

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {

    }

    // session容器销毁时
    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        // 判断当前用户是否已经登录
        HttpSession session = httpSessionEvent.getSession();
        Object user = session.getAttribute("user");
        // 不等于null说明登录了
        if(user != null) {
            // 获得容器对象
            ServletContext servletContext = session.getServletContext();
            // 获取在线统计人数
            Integer online = (Integer) servletContext.getAttribute("online");
            if (online == null) {
                online = 0;
            } else {
                online --;
            }
            servletContext.setAttribute("online", online);
        }
    }
}

在web.xml中配置监听器

  <listener>
        <listener-class>com.oa.listener.OnLineListener</listener-class>
    </listener>

在jsp页面中获取在线人数

<span>当前在线人数为:${applicationScope.online}</span>

HttpSessionBindingListener监听器的使用(监听单个Class,无需配置web.xml)

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class People implements Serializable, HttpSessionBindingListener {
    private String name;
    private int age;

    @Override
    public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {
        // 当该类被实例化绑定到Seesion中时执行(session.setAttribute)
        System.out.println("People被存到了session作用域中");
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {
        // 当该类的对象从Session中解绑时执行(session.removeAttribute)
        System.out.println("People从session作用域中被移除");
    }
}

使用HttpSessionActivationListener监听Session作用域中该JavaBean的钝化与活化

解析:
钝化:序列化到硬盘中
活化:反序列化到服务器中
所以需要钝化和活化的实体需要实现Serializable接口

配置tomcat的conf文件夹中的context.xml来开启钝化与活化功能

  • 该功能开启后,服务器一旦关闭,那么在关闭时会将所有存入到了Session中的值钝化(序列化)到服务器上
  • 并在服务器启动时将数据活化(反序列化)到服务器中。
  • 钝化后可以在相应位置看到以 .sessioin为后缀的文件
	<Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="true">
		<!-- 这里的directory实质上跑在服务器中的时候应该写相对路径,
		我这里由于使用idea演示,每次idea启动时都会复制一份新的tomcat
		这会导致钝化的session数据都消失掉了。
		-->
		<Store className="org.apache.catalina.session.FileStore" directory="D:\my_dev_tools\apache-tomcat-8.5.66-windows-x64\apache-tomcat-8.5.66\conf"/>
	</Manager>

配置一个JavaBean作为测试继承HTTP


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class People implements Serializable,HttpSessionActivationListener {
    private String name;
    private int age;


    @Override
    public void sessionWillPassivate(HttpSessionEvent httpSessionEvent) {
        // 钝化(序列化)
        System.out.println("钝化");
    }

    @Override
    public void sessionDidActivate(HttpSessionEvent httpSessionEvent) {
        // 活化(反序列化)
        System.out.println("活化");
    }
}

这里给个小建议,开发Servlet时,使用统一的前缀目录,这样更方便使用Filter拦截

例如:

  • /abc/employee/findOne
  • /abc/dept/findAll

版权声明:本文为itdqx原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/itdqx/p/14855958.html