JavaWeb三大组件之一Filter(过滤器)

定义

拦截所有访问web资源的请求或者响应(servlet、Jsp页面、HTML页面),从而实现我们自己的业务逻辑,这些逻辑可以是实现访问权限的控制、过滤敏感词、压缩响应等功能。

原理 

过滤器是"链接"在容器的处理过程中的,它会在servlet处理器之前访问进入的请求,并且在响应信息返回客服端之前访问这些响应信息。这样就可以动态的修改请求和响应中的内容。

创建Filter

创建web项目:

 

或者像这样建立web项目:

 

 

  

添加Tomcat服务器:

 

如果没有SmartTomcat,只有Tomcat像下面图片中这样:

 

则选择Tomcat里面的local,并向下面这样配置:

 

 

 

启动程序:

 

 

 添加java包和servlet依赖:

创建过滤器并实现它的方法:

 

在web.xml中注册Filter: 

 

当我们在执行的时候,发现会报404错误,那是因为我们没有重写Filter里面的init方法,在用Filter的时候必须重写init方法,因为web 工程启动的时候执行构造器方法和init 初始化方法 ,如果不重写初始化方法,就不会走过滤器。

重写Filter里面的init方法,然后执行过滤器:

 

 

我们会发现在浏览器中什么都没有输出,但是控制台中输出了doFilter里面内容,说明我们的过滤器起作用了 ,他拦截了所有的请求。当我们在doFilter方法里面加上下面的逻辑,浏览器就会输出内容:

 

 

 

上面filterChain里面的doFilter方法它的作用就是请求放行到下一个资源,这个资源有可能是过滤器,也可能是客户端。

 

Filter的生命周期 

 

上面每一步分别对应不同的方法:

实例化对应Filter的构造器方法,初始化对应init()方法、销毁对应的是destroy()方法 

Filter它是一个接口,该接口里面有如下几个方法:

 

destroy()

当web服务器调用该方法时,表示过滤器将被销毁。

init()

当web服务器调用该方法时,表示过滤器将被注册到服务中

doFilter()

当服务器调用Filter中的doFilter()方法,它会将每一个请求或者响应传递给下一个资源

具体实现:

 

 

1.可以看到Filter是在应用启动是被创建和初始化的。

 

 

2.Filter是单例多线程的(创建和初始化只会被执行一次) 

3.doFilter()方法无论是那一个线程访问,只要由该Filter进行过滤,那么就会执行该Filter的doFilter()方法,并且是每过滤一次就会执行一次doFilter()。

 

4.Filter中destroy方法是在应用被停止时调用的,它意味着销毁这个Filter。 

5.由于Filter是单列多线程的,所以为了保证线程安全,不能在Filter中定义可修改的成员变量,因为每个线程均可修改这个成员变量,这样就会带来线程安全问题。 

FilterConfig 

FilterConfig指的是Filter在web.xml中的注册信息 ,它将注册信息进行封装,然后通过形参的方式传给初始化方法。

FilterConfig里面的方法:

 

演示使用:

package com.xihua;

import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;


public class MyFilter implements Filter {
    private FilterConfig filterConfig;
    public MyFilter() {
        System.out.println("执行了MyFilter的构造方法");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        System.out.println("执行了init方法");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //获取过滤器的名字
        String filterName = filterConfig.getFilterName();
        System.out.println("过滤器的名字:" + filterName);
        //获取过滤器中所有的初始化参数名称
        Enumeration<String> names = filterConfig.getInitParameterNames();
        while (names.hasMoreElements()){
            String name = names.nextElement();
            //获取过滤器中初始化参数对应的值
            String value = filterConfig.getInitParameter(name);
            System.out.println(name + "=" + value);
        }
        //获取全局域
        ServletContext sc = filterConfig.getServletContext();
        System.out.println("servletContext=" + sc);

        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("执行了destory方法");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.xihua.MyFilter</filter-class>
        <init-param>
            <param-name>school</param-name>
            <param-value>xihua</param-value>
        </init-param>
        <init-param>
            <param-name>researcher</param-name>
            <param-value>jack</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

运行结果:

 

<filter-mapping> 

<filter-mapping>标签表示拦截匹配,也就是要拦截那些请求。

<filter-name>:要拦截的过滤器名称

<url-pattern>:拦截那些路径

<url-pattern>

注意Filter中在写拦截所有路径的时候只能写成/*,而不能写成/,因为写成/它就不走拦截器中的doFilter方法了。 

在Servlet中/*即会拦截动态资源又会拦截静态资源,而/不会拦截动态资源

演示Filter拦截动态和静态资源如下:

package com.xihua;

import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;


public class MyFilter implements Filter {
    public MyFilter() {
        System.out.println("执行了MyFilter的构造方法");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("执行了init方法");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("执行doFilter之前");
        System.out.println("执行doFilter之后");
    }

    @Override
    public void destroy() {
        System.out.println("执行了destory方法");
    }
}
package com.xihua.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行SomeServlet");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.xihua.MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <servlet>
        <servlet-name>SomeServlet</servlet-name>
        <servlet-class>com.xihua.servlet.SomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SomeServlet</servlet-name>
        <url-pattern>/SomeServlet</url-pattern>
    </servlet-mapping>
</web-app>

 

 

  

当将Filter中的拦截改为/时,过滤器将不起作用:

 

下面演示Servlet/*和/的区别: 

 

  

 

 

在<filter-mapping>标签中可以不使用<url-pattern>标签,但是需要指定<servlet-name>,这样就能指定拦截某个servlet而不拦截其他请求:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.xihua.MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <!--<url-pattern>/*</url-pattern>-->
        <servlet-name>SomeServlet</servlet-name>
    </filter-mapping>
    <servlet>
        <servlet-name>SomeServlet</servlet-name>
        <servlet-class>com.xihua.servlet.SomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SomeServlet</servlet-name>
        <url-pattern>/SomeServlet</url-pattern>
    </servlet-mapping>
</web-app>

 

 

Filter里面的<dispatcher>标签

dispatcher表示分发器,表示过滤器所拦截的资源被servlet容器调用的方式,可以是REQUEST,INCLUDE,FORWARD,ERROR中的任何一个,默认是REQUEST。用户可以设置多个<dispatcher>子元素用来指定过滤器对资源的多种调用方式进行拦截。

FORWARD表示当前过滤器只会拦截由一个Servlet通过RequestDispatcher的forward()完成跳转
INCLUDE表示当前过滤器只会拦截由一个Servlet通过RequestDispatcher的include()完成跳转
REQUEST表示当前过滤器会拦截普通请求,但对于forward()与include()的跳转不进行拦截,REQUEST是默认的。
ERROR表示当跳转到指定的错误处理页面时,这个跳转请求会被当前过滤器拦截

下面演示上面四个子元素的区别:

两个servlet:SomeServlet、OtherServelt

package com.xihua.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SomeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行SomeServlet");
        req.getRequestDispatcher("/otherServlet").forward(req,resp);
    }
}
package com.xihua.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class OtherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行了otherServlet");
    }
}

 在xml文件做如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--注册Filter-->
    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.xihua.MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--注册SomeServlet-->
    <servlet>
        <servlet-name>SomeServlet</servlet-name>
        <servlet-class>com.xihua.servlet.SomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SomeServlet</servlet-name>
        <url-pattern>/someServlet</url-pattern>
    </servlet-mapping>
    <!--注册OtherServlet-->
    <servlet>
        <servlet-name>OtherServlet</servlet-name>
        <servlet-class>com.xihua.servlet.OtherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>OtherServlet</servlet-name>
        <url-pattern>/otherServlet</url-pattern>
    </servlet-mapping>
</web-app>

执行someServlet请求,因为在someServlet中我们设定了一个请求转发,观察拦截器的拦截效果:

 

再来看一下在<filter>标签里面添加<dispatcher>标签,并设定子元素为FORWARD:

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

 

   

将请求转发的关键字换成include:

 

  

出现上面的原因是因为:

 

 

请求转发中forward和include的区别在于响应的标准输出流开启时间不一样,forward会请求传递给最后一个servlet,当最后一个servet执行完以后才会开启/返回响应。而include它是将其他请求包在第一个请求里面,并由第一个请求开启/返回响应。

 

Filter对请求和响应的修改 

 

 

 

 

多个Filter的执行顺序

两个Filter:MyFilter、UsFilter,在xml文件中先配置MyFilter,后配置UsFilter

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--注册MyFilter-->
    <filter>
        <filter-name>MyFilter</filter-name>
        <filter-class>com.xihua.MyFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>MyFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--注册UsFilter-->
    <filter>
        <filter-name>UsFilter</filter-name>
        <filter-class>com.xihua.UsFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>UsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!--注册SomeServlet-->
    <servlet>
        <servlet-name>SomeServlet</servlet-name>
        <servlet-class>com.xihua.servlet.SomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SomeServlet</servlet-name>
        <url-pattern>/someServlet</url-pattern>
    </servlet-mapping>

</web-app>

 

 

交换xml文件中的配置顺序:

 

总结:多个Filter的执行顺序,与Filter的配置顺序有关。 

 

Filter的执行原理 

Servlet的执行原理:在Servlet中有两个Map,这两个Map的key均为Servlet注册时的<url-pattern/>值,但value是不同的。第一个Map的value是Servlet实例对象的引用,第二个Map的value为<servlet-class/>的值,即Servlet类的全限定类名。

执行原理:

当对Servlet的请求到达Servlet容器时,会先对请求进行解析,使用该解析出的URL,作为比较对象,从第一个Map中查找是否有匹配的key,若不存在匹配的key,那么读取其value,即Servlet对象的引用,执行该Servlet的service()方法。

若不存在匹配的key ,那么再从第二个Map中查找是否有匹配的key。若存在,这读取其value,即读取其value,即要访问的Servlet的全限定类名。然后使用反射机制创建该Servlet实例,并将该实例写入到第一个Map中,然后在执行该Servlet的service()方法。

若第二个Map中也没有找到匹配的key,那么就跳转到错误处理页面404。

Filter的执行原理:

一个数组与一个Map :

一个Map:Map的key为<url-pattern/>的值,value为Filter实例对象的引用

一个数组:存在着与请求相匹配的所有Filter

执行原理:

当对某资源的请求到达Web容器时,会先对请求进行解析,使用解析出的URI作为比较对象,从Map中查找是否存在相匹配的key。若存在,那么读取其value,即Filter对象的引用,将该应用存入到数组中。然后继续向后查找,直到将Map查找完毕。这样在数组中就会存在按照查找顺序排好序的Filter引用。

数组初始化完毕后,开始按照数组元素顺序进行执行。所有数组中的Filter全部执行完毕后,再跳转到请求的目标资源。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸鱼吐泡泡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值