Filter,Listener(九)

483 阅读9分钟

Filter

简介

​ 它是用来过滤响应和请求的,在请求或者响应之前先执行一部分默认工作。比如在我们编写Java Web项目时,会使用Servlet,但是由于客户端和服务器之间的默认编码集不同,每写一个servlet都要重新设置编码。此时,我们就可以使用交给过滤器来做【除此之外的应用场景有:登录权限检查,解决网站乱码,过滤敏感字符等等】。

使用

Filter是服务器端三大组件之一。【还包括Servlet\Listener】

服务器组件共性

  1. 都需要实现某个接口
  2. 都需要注册【在web.xml中或者直接使用注解来注册】

常用的API

  • Filter接口:javax.servlet.Filter

    • init(FilterConfig config):初始化方法
    • doFilter(ServletRequest req,ServletResponse res,FilterChain chain)
      • 过滤请求&响应
    • destroy():销毁方法
  • FilterConfig:封装Filter配置信息,每个Filter对应唯一一个FilterConfig对象。

    • getFilterName():获取Filter名称
    • getServletContext():获取servlet上下文对象
    • getInitParameter():获取初始化参数
  • FilterChain:封装Filter链信息

    • doFilter():方法用于调用Filter链上的下一个过滤器,如果当前过滤器为最后一个过滤器则将请求发送到目标资源。【放行请求】

代码示例

public class HelloWorldFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("HelloWorldFilter放行前");
        chain.doFilter(req, resp);
        System.out.println("HelloWorldFilter放行后");
    }

    public void init(FilterConfig config) throws ServletException {

    }

}
<filter>
        <filter-name>HelloWorldFilter</filter-name>
        <filter-class>com.dyy.filter.HelloWorldFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HelloWorldFilter</filter-name>
<!--        对于过滤器,url一般不会设置/FilterName-->
        <url-pattern>/HelloWorldFilter</url-pattern>
    </filter-mapping>

Filter工作原理【单个Filter】

  1. 请求被Feilter过滤URL
  2. 执行Filter中doFilter()方法的方形前代码,过滤请求
  3. 执行chain.doFilter(request,response);放行请求,执行目标资源【Servlet】,吃力请求,做出响应。
  4. 执行FIlter中doFilter()方法的放行后代码,过滤响应
  5. 响应

Filter生命周期

  1. 构造器
    • 执行时机:启动服务器时,创建Filter
    • 执行次数:在整个生命周期中,只执行一次【单例】。
  2. init()方法
    • 执行时机:启动服务器时,执行构造器之后,执行init方法
    • 执行次数:在整个生命周期中,只执行一次。
  3. doFilter()方法
    • 执行时机:每次触发指定URL时,执行doFilter()【过滤请求,响应】
    • 执行次数:在整个生命周期中,执行多次。
  4. destroy()放啊
    • 执行时机:在关闭服务器时,执行
    • 执行次数:在整个生命周期中,执行一次。

Filter工作原理【多个Filter】

  1. 请求被Filter过滤URL;
  2. 执行Filter1中的doFilter()方法的放行前代码,过滤请求,执行chain.doFilter(request,response);放行请求;
  3. 执行Filter2中的doFilter()方法的放行前代码,过滤请求,执行chain.doFilter(request,response);放行请求;
  4. 执行目标资源【Servlet】,处理请求,做出响应;
  5. 执行Filter2中的doFilter()方法的放行后代码,过滤响应;
  6. 执行Filter1中的doFilter()方法的放行后代码,过滤响应;
  7. 响应。

怎样确定谁是Filter1谁是Filter2【Filter执行顺序】?

​ 如果是在web.xml中注册的filter,那么由filter-mapping顺序来决定的;如果是在注解中注册的,那么是由filter-name名称字母顺序来决定的,比如:aFilter>bFilter

Filter中URL匹配规则

精确匹配

1 url匹配
 <filter>
        <filter-name>Filter1</filter-name>
        <filter-class>com.dyy.filter.Filter1</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Filter1</filter-name>
<!--        对于过滤器,url一般不会设置/FilterName-->
        <url-pattern>/TestFilterServlet</url-pattern>
    </filter-mapping>
2 名称匹配
<filter>
        <filter-name>Filter2</filter-name>
        <filter-class>com.dyy.filter.Filter2</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Filter2</filter-name>
<!--        <url-pattern>/TestFilterServlet</url-pattern>-->
        <servlet-name>TestFilterServlet</servlet-name>
    </filter-mapping>

模糊匹配【带有符号(*)匹配】

1 后缀名匹配:【*】书写在URL最前面

比如:

<url-pattern>*.html</url-pattern>
2 目录匹配:【*】书写在URL最后面

比如

<url-pattern>/pages/*</url-pattern>

注意:

【*】不能写在URL的中间位置

终版Filter【HttpFilter】

为什么需要使用HttpFilter

​ 类比Servlet,javax.servlet.Servlet接口和javax.servlet.http.HttpServlet类,我们可以总结HttpServlet的优势有以下几点:

  1. 提供获取对象方法【getServletConfig()和getServletContext】
  2. 将service()方法,抽象化【在子类中只关注service()】
  3. 重写service():类型转换
  4. 重载service():通过请求方式不同,调用不同的处理方法。

HttpFilter这个抽象类是我们仿照HttpServlet类的思路来写的,因此类似的,它也有上面列出来的这些优点。

手动搭建HttpFilter思路

  1. 提供获取对象方法【getFilterConfig()和getServletContext()】
  2. 将doFilter()方法,抽象化【在子类中只关注doFilter()】
  3. 重载doFilter()方法,类型转换。
  4. 注意:HttpFilter是抽象类,不能实例化,所以不能注册。

示例代码

/**
 * @author Chunsheng Zhang 尚硅谷
 * @create 2021/8/18 16:19
 */
public abstract class HttpFilter implements Filter {

    private FilterConfig filterConfig;

    /**
     * 获取FilterConfig对象方法
     * @return
     */
    public FilterConfig getFilterConfig() {
        return filterConfig;
    }

    /**
     * 获取ServletContext对象方法
     * @throws ServletException
     */
    public ServletContext getServletContext(){
        return getFilterConfig().getServletContext();
    }


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;
        doFilter(req,res,chain);
    }

    public abstract void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException;


    @Override
    public void destroy() {

    }
}

基于web.xml注册Filter

<filter>
        <filter-name>Filter3</filter-name>
        <filter-class>com.dyy.filter.Filter3</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Filter3</filter-name>
        <url-pattern>*.html</url-pattern>
    </filter-mapping>

基于注解方式注册Filter

@WebFilter(filterName = "Filter3",value = "*.html")
public class Filter3 implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("Filter3.....");
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

如何解决使用Filter后乱码的问题

如果使用filter来解决页面乱码问题,css,js文件的uri就不可用了。需要分别处理。

@WebFilter(filterName = "Filter3",value = "*.html")
public class Filter3 implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest reqest = (HttpServletRequest) req;
        String uri = reqest.getRequestURI();
        if(uri.contains(".js")||uri.contains(".css")){
            chain.doFilter(req, resp);
        }else {
            req.setCharacterEncoding("UTF-8");
            resp.setContentType("text/html;charset=UTF-8");
            chain.doFilter(req, resp);
        }
    }

    public void init(FilterConfig config) throws ServletException {

    }

}

listener

观察者模式

观察者模式是二十三中设计模式之一,它是指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式。

观察者:监控被观察者的行为,一旦发现被观察者触发了时间,就会调用实现准备好的方法执行操作。

被观察者:被观察者一旦触发了被监控的事件,就会被观察者发现。

简介

​ 监听器:专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。

​ Servlet监听器:Servlet规范中定义的一种特殊类,它用于监听Web应用程序中的ServletContext,HttpSession 和HttpServletRequest等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。

分类

1 ServletContextListener

作用:监听ServletContext对象的创建与销毁。

方法:

contextInitialized(ServletContextEvent sce)——>ServletContext创建时调用

contextDestroyed(ServletContextEvent sce)——>ServletContext销毁时调用

参数:

ServletContextEvent对象代表从ServletContext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象。

2 HttpSessionListener

作用:监听HttpSession对象的创建与销毁

sessionCreated(HttpSessionEvent hse)

sessionDestroyed(HttpSessionEvent hse)

参数:

HttpSessionEvent对象代表从HttpSession对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpSession对象

3 ServletRequestListener

作用:监听ServletRequest对象的创建与销毁

方法:

requestInitialized(ServletRequestEvent sre)——>ServletRequest对象创建时调用

requestDestroyed(ServletRequestEvent sre)——>ServletRequest对象销毁时调用

参数:

ServletRequestEvent对象代表从HttpServletRequest对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpServletRequest对象。另外还有一个方法可以获取到当前Web应用的ServletContext对象。

4 ServletContextAttributeListener

作用:监听ServletContext中属性的创建、修改和销毁

attributeAdded(ServletContextAttributeEvent scab)——>向ServletContext中添加属性时调用

attributeRemoved(ServletContextAttributeEvent scab)——>从ServletContext中移除属性时调用

attributeReplaced(ServletContextAttributeEvent scab)——>当ServletContext中的属性被修改时调用

参数:

ServletContextAttributeEvent对象代表属性变化事件,包含以下方法:

getName()——获取修改或添加的属性名

getValue()—— 获取被修改或添加的属性值

getServletContext()——获取ServletContext对象

5 HttpSessionAttributeListener

作用:监听HttpSession中属性的创建、修改和销毁

方法:

attributeAdded(HttpSessionBindingEvent se)——向HttpSession中添加属性时调用

attributeRemoved(HttpSessionBindingEvent se)——从HttpSession中移除属性时调用

attributeReplaced(HttpSessionBindingEvent se)——当HttpSession中的属性被修改时调用

参数:

HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:

getName()——获取修改或添加的属性名

getValue()—— 获取被修改或添加的属性值

getSession()——获取触发事件的HttpSession对象

6 ServletRequestAttributeListener

作用:监听ServletRequest中属性的创建、修改和销毁

方法:

attributeAdded(ServletRequestAttributeEvent srae)——向ServletRequest中添加属性时调用

attributeRemoved(ServletRequestAttributeEvent srae)—— 从ServletRequest中移除属性时调用

attributeReplaced(ServletRequestAttributeEvent srae)——当ServletRequest中的属性被修改时调用

参数:

ServletRequestAttributeEvent对象代表属性变化事件,它包含的方法如下:

getName()——获取修改或添加的属性名

getValue()—— 获取被修改或添加的属性值

getServletRequest ()——获取触发事件的ServletRequest对象

7 HttpSessionBindingListener

作用:监听某个对象在Session域中的创建与移除

valueBound(HttpSessionBindingEvent event)——该类的实例被放到Session域中时调用

valueUnbound(HttpSessionBindingEvent event)——该类的实例从Session中移除时调用

参数:

getName()——获取修改或添加的属性名

getValue()—— 获取被修改或添加的属性值

getSession()——获取触发事件的HttpSession对象

8 HttpSessionActivationListener

作用:监听某个对象在Session中的序列化与反序列化。

sessionWillPassivate(HttpSessionEvent se)——该类实例和Session一起钝化到硬盘时调用

sessionDidActivate(HttpSessionEvent se)——该类实例和Session一起活化到内存时调用

参数:

HttpSessionEvent对象代表事件对象,通过getSession()方法获取事件涉及的HttpSession对象。

ServletContextListener的使用

作用

​ ServletContextListener是监听ServletContext对象的创建和销毁的,因为ServletContext对象是在服务器启动的时候创建、在服务器关闭的时候销毁,所以ServletContextListener也可以监听服务器的启动和关闭。

使用场景

​ SpringMVC会用到一个ContextLoaderListener,这个监听器就实现了ServletContextListener接口,表示对ServletContext对象本身的生命周期进行监控。

代码实现
  1. 创建监听器类

    package com.atguigu.listener;
    
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    
    /**
     * 包名:com.atguigu.listener
     *
     * @author Chunsheng Zhang
     * 日期2021-05-18  14:10
     * ServletContextLisneter监听器可以监听服务器的启动和关闭
     * 1. contextInitialized()方法可以监听服务器的启动
     * 2. contextDestroyed()方法可以监听服务器的关闭
     */
    public class MyContextListener implements ServletContextListener {
    
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            System.out.println("服务器启动了...");
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            System.out.println("服务器关闭了...");
        }
    }
    
    
    1. 注册监听器
    <!--配置Listener-->
    <listener>
        <listener-class>com.atguigu.listener.MyContextListener</listener-class>
    </listener>
    

    不需要注册uri(现实中监听也都是匿名的,不会特意告诉被监听人监听了什么)。