过滤器

过滤器

Java Web的三大组件

Servlet中组件一共有三种:Servlet过滤器监听器

组件 作用 实现接口
Servlet 是一个运行在服务器端的Java小程序,用来接收请求并做出响应 javax.servlet.Servlet
过滤器 用于拦截用户的请求和响应,并且修改请求中的数据,对数据进行处理 javax.servlet.Filter
监听器 监听Web程序在运行过程中对作用域操作的事件,并且对事件进行处理 多个不同的接口 javax.servlet.XxxListener

过滤器的概念

过滤器是服务器与客户端请求与响应的中间层组件,在实际项目开发中过滤器主要用于对浏览器的请求进行过滤处理,将过滤后的请求再转给下一个资源。与其他的WEB应用程序组件不同的是,过滤器是采用了“链”的方式进行处理的

这里写图片描述

过滤器的使用场景:

  1. 对用户登录权限进行拦截
  2. 实现一些日志记录的功能
  3. 集中处理处理一些公共的功能,如:汉字编码和解码

过滤器的执行特点:

与Servlet的执行不同,Servlet是有访问的地址。不是由用户主动调用,而是自动执行,是通过匹配用户的访问地址去进行过滤。

过滤器编写步骤:

  1. 开发过滤器的步骤:

    1. 编写一个类,实现javax.servlet.Filter接口
    2. 实现接口中所有的方法,其中doFilter()就是执行过滤任务
    3. 过滤器需要在web.xml中进行配置,配置与Servlet类似。
  2. 示例:创建一个过滤器HelloFilter,在运行HelloServlet前和后分别输出一句话,在HelloServlet中也输出一句话,观察控制台的运行效果。HelloServlet代表Web资源

image

/**
 * a)   编写一个类,实现javax.servlet.Filter接口
 * @author NewBoy
 *
 */
public class HelloFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    /**
     * 执行过滤任务
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("请求经过过滤器");
        //放行,让请求继续到达web资源(Servlet),调用chain中的方法
        chain.doFilter(request, response);
        System.out.println("响应回来经过过滤器");
    }

    @Override
    public void destroy() {

    }

}

Servlet

public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    System.out.println("我是HelloServlet,Web资源");
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    doGet(request, response);
}
配置文件

<filter>
    
    <filter-name>hellofilter-name>
    
    <filter-class>com.filter.HelloFilterfilter-class>
filter>
<filter-mapping>
    
    <filter-name>hellofilter-name>
    
    <url-pattern>/*url-pattern>
filter-mapping>

过滤器的执行流程

过滤器_第1张图片

  1. 用户发送请求,如果请求的地址匹配过滤器的url-pattern,则执行过滤器
  2. 执行过滤器中的doFilter方法,由chain.doFilter方法对请求进行放行
  3. 到达Web资源,响应回来的时候会再次经过过滤,执行响应的代码。

过滤器的生命周期:

  • 过滤器加载的时机:

    • Servlet是用户第1次访问的时候实例化,并且初始化。

    • 过滤器是在Web服务器启动加载当前项目完毕以后自动实例化

    image

生命周期的方法:

Filter接口中的方法
- void init(FilterConfig filterConfig)

- 在初始化过滤器的时候**执行1次**

- void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- 参数:请求,响应,过滤链

- 请求和响应这两个接口是HttpServletRequest和HttpServletResponse的父接口

- 执行过滤的功能,**每次请求都会执行这个方法**

- public void destroy()
- 在服务器关闭的时候销毁,执行1次


过滤器的映射路径

Filter与Servlet中的区别:

  • 在Servlet中url-pattern是访问的地址

  • 在Filter中url-pattern是要过滤的地址

问:浏览器访问目标资源的路径,如果目标地址不存在,过滤器会不会运行?

如果Web资源不存在,只要匹配过滤的地址,同样会执行过滤器

在Filter中URL的过滤方式

匹配方式 匹配哪些资源 示例
完全匹配 必须与访问地址精确匹配 /demo1/aaa/bbb
目录匹配 匹配某一个目录下所有的Web资源 /aaa/*/*匹配所有的资源
扩展名匹配 *.扩展名 匹配某一类扩展名 *.do*.action

有关匹配的要点:

  • 以/开头的匹配模式和以扩展名结尾的配置方式,同时出现会怎样?

同时出现会在web项目加载的时候就失败,导致当前项目所有的web资源都无法访问。

image

过滤多个地址的写法:

一个filter-mapping中包含多个url-pattern

<filter>
    <filter-name>lifefilter-name>
    <filter-class>com.filter.Demo1LifeCycleFilterfilter-class>
filter>
<filter-mapping>
    <filter-name>lifefilter-name>
    <url-pattern>/demo1url-pattern>
    <url-pattern>/demo2url-pattern>
filter-mapping>

一个filter对应多个filter-mapping

<filter>
    <filter-name>lifefilter-name>
    <filter-class>com.filter.Demo1LifeCycleFilterfilter-class>
filter>
<filter-mapping>
    <filter-name>lifefilter-name>
    <url-pattern>/demo1url-pattern>
filter-mapping>
<filter-mapping>
    <filter-name>lifefilter-name>
    <url-pattern>/demo2url-pattern>
filter-mapping>

过滤Servlet的写法:

<filter>
    <filter-name>lifefilter-name>
    <filter-class>com.filter.Demo1LifeCycleFilterfilter-class>
filter>
<filter-mapping>
    <filter-name>lifefilter-name>
    
    <servlet-name>HelloServletservlet-name>
filter-mapping>

过滤器的拦截方式:

默认的拦截方式

  • 过滤器的拦截方式一共有4种
    REQUEST、FORWARD、INCLUDE、ERROR

  • 默认是请求的方式:只有直接来源于浏览器的请求,才经过过滤器。

来自转发的请求被拦截

  1. 在index.jsp转发到HelloServlet
<jsp:forward page="/demo1">jsp:forward> 发现没有经过过滤器,但servlet还是访问了。
  1. 过滤器的配置
<filter>
    <filter-name>lifefilter-name>
    <filter-class>com.filter.Demo1LifeCycleFilterfilter-class>
filter>
<filter-mapping>
    <filter-name>lifefilter-name>
    <url-pattern>/demo1url-pattern>
    
    <dispatcher>FORWARDdispatcher>
filter-mapping>
  • 这样子转发也经过了过滤器

同时写多个拦截方式

<filter>
    <filter-name>lifefilter-name>
    <filter-class>com.filter.Demo1LifeCycleFilterfilter-class>
filter>
<filter-mapping>
    <filter-name>lifefilter-name>
    
    
    <url-pattern>/demo1url-pattern>
    
    <dispatcher>FORWARDdispatcher>
    
    <dispatcher>REQUESTdispatcher>
filter-mapping>

过滤器的拦截类型小结

过滤类型 作用
REQUEST 直接来源于浏览器的访问地址,(包含重定向)
FORWARD 转发的时候经过过滤器
INCLUDE 页面包络另一个页面访问时也经过过滤器
ERROR 页面错误时经过过滤器

FilterConfig对象

  • 什么是FilterConfig对象:
    用于得到过滤器中的配置参数
<filter>
    <filter-name>Demo2ConfigFilterfilter-name>
    <filter-class>com.filter.Demo2ConfigFilterfilter-class>
    
    <init-param>
        <param-name>charsetparam-name>
        <param-value>GBKparam-value>
    init-param>
    <init-param>
        <param-name>countryparam-name>
        <param-value>Chinaparam-value>
    init-param>
filter>
<filter-mapping>
    <filter-name>Demo2ConfigFilterfilter-name>
    <url-pattern>/*url-pattern>
filter-mapping>

FilterConfig三个方法:

方法名 功能
String getInitParameter(String name) 通过初始参数名得到参数值
Enumeration getInitParameterNames() 得到所有的初始参数名字,返回枚举类
ServletContext getServletContext() 得到上下文对象

- 在过滤器初始化init方法中把上面的配置参数输出

image

//得到初始的配置参数
String charset = filterConfig.getInitParameter("charset");
String country = filterConfig.getInitParameter("country");
System.out.println(charset);
System.out.println(country);
//得到所有的配置参数名
Enumeration names = filterConfig.getInitParameterNames();
//遍历
while (names.hasMoreElements()) {
    //得到其中的一个名字
    String name = names.nextElement();
    System.out.println("名字:" + name + ", 值:" + filterConfig.getInitParameter(name));
}

案例:POST解码

  • 需求:编写过滤器,过滤所有Servlet中使用POST方法提交的汉字的编码。
    1. 有2个Servlet,一个是LoginServlet登录,一个是RegisterServlet注册
    2. 有2个JSP页面,1个是login.jsp,有表单,登录名。1个register.jsp,有表单,有注册的名字。都使用POST提交用户名使用汉字提交。
    3. 使用过滤器,对所有的Servlet的POST方法进行过滤
    4. 过滤的编码参数通过filterConfig得到
    5. 在没有使用过滤器之前,每个Servlet必须加上汉字编码:request.setCharacterEncoding(字符集); 字符集与网页的编码要一致

过滤器_第2张图片

//过滤的编码参数,通过filterConfig得到
@Override
public void init(FilterConfig config) throws ServletException {
    charset = config.getInitParameter("charset");
    System.out.println(charset);
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    //设置汉字的编码, charset相当于utf-8
    request.setCharacterEncoding(charset);
    //放行
    chain.doFilter(request, response);
}

过滤器配置


<filter>
  <filter-name>EncodingFilterfilter-name>
  <filter-class>com.filter.EncodingFilterfilter-class>
  
  <init-param>
    <param-name>charsetparam-name>
    <param-value>utf-8param-value>
  init-param>
filter>
<filter-mapping>
  <filter-name>EncodingFilterfilter-name>
  
  <url-pattern>/loginurl-pattern>
  <url-pattern>/registerurl-pattern>
filter-mapping>

过滤链

过滤器_第3张图片

什么是过滤器链:

一次请求可以同时经过多个过滤器,每个过滤器会将请求传递给下一个过滤器,如果下一个没有过滤器了,则传递给Web资源,这多个过滤器就组成了一个过滤器链。请求的时候经过每一个过滤器,响应的时候以相反顺序再回到每一个过滤器。

过滤器的顺序是根据配置文件中配置的顺序

Filter接口:

Filter接口中的方法:过滤器接口

  • 生命周期的方法
Filter接口中的方法 参数的作用
doFilter(ServletRequest request, ServletResponse response, FilterChain chain) **ServletRequest**:代表请求对象是HttpServletRequest接口的父接口,如果要使用HttpServletRequest对象,**需要对类型进行强转**。**ServletResponse**: 代表响应对象是HttpServletResponse的父接口,如果要使用HttpServletResponse对象,**需要对类型进行强转**。**FilterChain**:代表过滤链对象用于请求的放行。

FilterChain接口中的方法:过滤链接口

FilterChain接口中的方法 参数的作用
放行
doFilter(ServletRequest request, ServletResponse response) ServletRequest:代表请求对象,是HttpServletRequest接口的父接口,如果要使用HttpServletRequest对象,需要对类型进行强转。
ServletResponse: 代表响应对象,是HttpServletResponse的父接口,如果要使用HttpServletResponse对象,需要对类型进行强转。
将上一个请求传递给下一个过滤器或Web资源

示例:过滤器链

  • 需求:创建两个过滤器OneFilter和TwoFilter,访问ResourceServlet,每个过滤器的请求和响应各输出一句话,观察过滤器的执行过程。

过滤器_第4张图片

第一个过滤器

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    //处理请求
    System.out.println("请求经过了第1个过滤器");
    //放行
    chain.doFilter(request, response);
    //处理响应
    System.out.println("响应经过了第1个过滤器");
}

第二个过滤器

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    //处理请求
    System.out.println("请求经过了第2个过滤器");
    //放行
    chain.doFilter(request, response);
    //处理响应
    System.out.println("响应经过了第2个过滤器");
}

*配置参数的顺序!!*


<filter>
    <filter-name>OneFilterfilter-name>
    <filter-class>com.filter.OneFilterfilter-class>
filter>
<filter-mapping>
    <filter-name>OneFilterfilter-name>
    <url-pattern>/*url-pattern>
filter-mapping>


<filter>
    <filter-name>TwoFilterfilter-name>
    <filter-class>com.filter.TwoFilterfilter-class>
filter>
<filter-mapping>
    <filter-name>TwoFilterfilter-name>
    <url-pattern>/*url-pattern>
filter-mapping>

web.xml中出现的顺序,哪个配置在前面,哪个就是前面的过滤器。


案例

过滤器_第5张图片

实现步骤:
1. 在WebRoot下创建页面 login.jsp上使用${msg},显示信息。
1. 创建LoginServlet, 判断用户名密码是否正确,如果正确,则在会话域中保存用户信息。登录成功跳转到list.jsp,登录失败则在域中写入登录失败的信息,并且跳转到login.jsp。

  1. 使用过滤器解决:创建LoginAuthorityFilter

    • 创建成员变量HashSet,用于保存所有不经过过滤器的页面

    • 在init方法中添加登录页面和登录的Servlet到HashSet中

    • 在doFilter过滤的方法中获取当前访问的路径,判断当前路径是否在集合中,如果在集合中则放行并返回
    • 否则判断会话域中是否有指定的用户信息,如果有则放行,并返回
    • 最后重定向到登录页面

过滤器_第6张图片

过滤器
public class LoginAuthorityFilter implements Filter {

    //创建成员变量HashSet,用于保存所有不经过过滤器的页面
    private HashSet paths = new HashSet();

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        //得到子接口
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //得到会话域
        HttpSession session = request.getSession();
        //在doFilter过滤的方法中获取当前访问的路径,判断当前路径是否在集合中
        String path = request.getServletPath();
        System.out.println(path);
        if (paths.contains(path)) {
            //如果在集合中则放行并返回
            chain.doFilter(request, response);
            return;
        }
        //否则判断会话域中是否有指定的用户信息
        String user = (String) session.getAttribute("user");
        if (user != null) {
            //如果有则放行,并返回
            chain.doFilter(request, response);
            return;
        }
        System.out.println("拦截到非法的用户:" + request.getRemoteAddr());
        //最后重定向到登录页面
        response.sendRedirect(request.getContextPath() + "/login.jsp");
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
        // 在init方法中添加登录页面和登录的Servlet到HashSet中
        paths.add("/login.jsp");
        paths.add("/login");
    }

}

servlet
HttpSession session = request.getSession();
//得到用户名和密码
String name = request.getParameter("user");
String pwd = request.getParameter("pwd");
//判断是否登录成功
if ("newboy".equals(name) && "123".equals(pwd)) {
    //如果登录成功,将用户的信息保存到会话域中
    session.setAttribute("user", name);
    //跳转到list.jsp
    response.sendRedirect(request.getContextPath() + "/list.jsp");
}
//如果登录失败,写错误信息到请求域,跳转到login.jsp显示出来
else {
    request.setAttribute("msg", "用户名或密码不正确");
    request.getRequestDispatcher("/login.jsp").forward(request, response);
}

你可能感兴趣的:(JavaWeb)