美文网首页
第18讲.Filter&Listener

第18讲.Filter&Listener

作者: 祥祺 | 来源:发表于2018-12-01 13:16 被阅读0次

学习摘要:

今天讲的内容web的两大组件:Filter(过滤器)和Listener(监听器),重点掌握Filter(过滤器)。

了解什么是过滤器,过滤器的作用,掌握过滤器的基本使用,掌握过滤器在实际开发中的应用。

了解什么是监听器,监听器的作用,了解常见的监听器

Filter(过滤器)

课前引导为何要学习过滤器?

我们之前做的商品列表页面,不想让用户直接访问,必须先登录,之前的思路是在商品列表页面中添加验证用户是否登录。那么这种解决方案,会带来一个问题,如果有很多列表页面都必须登录才能访问呢?如果每个列表页面都添加相同的验证操作,那么就出现代码重复的问题,也就导致维护起来困难的问题,如何处理此问题呢?解决方案就是使用过滤器,所以这就是学习过滤器的原因。

过滤器的概述

过滤器在百度百科中的概念:

过滤器 技术是servlet 2.3新增加的功能。servlet2.3是sun公司于2000年10月发布的过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应。

在我们生活中,常见的过滤器都有哪些呢?
比如:带有过滤功能的净水器(其目的是过滤水中的杂质),香烟的过滤嘴(其目的是过滤烟叶中的焦油和燃烧时产生的悬浮粒子),丈母娘(其目的是过滤未来女婿的条件是否门当户对)

在开发中,程序中的过滤器是什么?

在java中,最小的程序单元是,而过滤器也是一个类,只不过是一个特殊的类,就像我们之前学习的Servlet一样,什么是Servlet?定义一个普通的类,实现Servlet接口,这个类就被称为Servlet。那么我们的Filter也是一样的,定义一个普通的类,实现Filter接口,这个类就称为Filter(过滤器)。

Servlet和Filter都是javaWeb的一个组件

过滤器在开发中的作用是什么呢?

1.png

Web中过滤器的作用(简单理解为:过滤处在客户端和服务端资源之间):

过滤器可以对所有的请求或者响应做拦截操作.

把客户端和服务端的交互看作是从广州到河北,如上图,广州是客户端,河北是服务端。中间的大山就是过滤器,那么要从广州到河北必须经过一个座山,无论是从广州去河北,还是从河北回来广州,都会经过此山。

过滤器的特点:

1:以常规的方式调用资源(Servlet/JSP);
就好比上图中从广州到河北,虽然经过了一座山,但是没有遇到土匪,顺利经过此山。

2:利用修改过的请求信息调用资源;

就好比上图中从广州到河北,带了礼物月饼,经过一座山的时候,遇到了土匪,最后土匪把月饼扣下来了,对人和车进行了放行。

3:调用资源之后,在响应到客户端之前,对响应做出修改;

就好比上图中从广州到河北,从河北回到广州的时候,带回了很多特产(肉和果子),在回来的途中还是经过一座山,在山上遇到了土匪,最后土匪把肉和果子扣下,对人和车放行。

4:阻止当前资源调用,代之转到其他资源.

就好比上图中从广州到河北,经过一座山的时候,遇到了土匪,因个人长得漂亮,最后土匪把人给扣下做了压寨夫人。

过滤器在开发中的应用

过滤器在开发中的运用:

1):可以对请求中的字符做编码.

2.png

正如上图中,处理post请求的乱码需要在Servlet中设置reqeust.setCharacterEncoding("utf-8");如果有很多个Servlet,那么需要设置很多次,在很多Servlet都编写了重复的代码,意味着代码的维护成本太高。因为每次请求Servlet都要经过过滤器,所以只需要在过滤器中设置一次即可。
所以更合理的做法是把设置编码的工作交给过滤器去完成。

2):登陆验证过滤器.

3.png

如上图中,要想访问Servlet1和Servlet2, 需要登录才能访问。那么我们立刻想到的做法就是在Servlet1和Servlet2中分别添加校验代码,从session中获取用户信息,如果获取到信息认为用户已经登陆,否则是用户没有登陆,重定向到登录页面。
那么在Servlet1和Servlet2中添加了相同的校验用户信息的代码,代码重复也是存在代码不好维护的问题。因为每次请求Servlet都要经过过滤器,所以只需要在过滤器中设置一次即可。所以更合理的做法是把登陆校验的工作交给过滤器去完成。

3):敏感字(非法文字)过滤.

4.png
在一些特殊的开发工作中,比如一些留言板,那么如果留言的内容出现了脏话,反动的内容等,那么需要对这些内容作处理。检索出这些字,会进行和谐掉。如上图中,上传内容是“傻逼”,那么应该在Servlet中接收到这些文字的时候进行判断,发现接收的内容是“傻逼”,那么把内容换成“帅锅”,这个替换的工作如果放在Servlet中,那么如果有很多个Servlet,也是同样替换敏感字的代码也要在每个Servlet中添加。造成代码出现重复的问题。也意味着不好维护。所以合理的做法应该是把替换敏感字的代码放入到过滤器中完成。

过滤器的开发和使用

回忆一下之前学习的Servlet,如何定义一个Servlet呢?

1:自定义一个类(XxxServlet),实现于javax.servlet.Servlet接口.

2:实现Servlet接口中的方法(init(初始化方法),service(处理请求)).

3:告诉Tomcat来帮我们管理该Servlet程序
(1:使用web.xml做配置,2:WebServlet("/资源名")).

  <servlet>
    <servlet-name>Servlet的别名</servlet-name>
     <servlet-class>自定义Servlet的全限定名</servlet-class>
  </servlet>
  <servlet-mapping>
     <servlet-name>Servlet的别名</servlet-name>
     <url-pattern><font color='red'>/资源名称</font></utl-pattern>
  </servlet-mapping>
 注意:此时的url-pattern的文本内容是外界访问Servlet的资源名称. 

如何定义一个Filter呢?

1:自定义一个类(XxxFilter),实现于javax.servlet.Filter接口.

2:实现Filter接口中的方法(init(初始化方法),doFilter(执行过滤操作)).

3:告诉Tomcat来帮我们管理该Filter程序
(1:使用web.xml做配置,2:WebFilter("/资源名")).

<filter>
     <filter-name>Filter的别名</filter-name>
     <filter-class>自定义Filter的全限定名</filter-class>
</filter>
<filtert-mapping>
     <filter-name>Filter的别名</filter-name>
     <url-pattern><font color='blue'>/资源名称</font></utl-pattern>
</filter-mapping>
注意:此时的url-pattern的文本内容是Filter对哪一些资源做过滤操作.  
如: 
   /hello.jsp  :说明当前Filter只会对/hello.jsp做拦截/过滤.<br/>
  /employee   :说明当前Filter只会对/employee资源做过滤.<br/>
  /*          :说明当前Filter对所有请求都有拦截<br/>
  /system/*   :说明当前Filter只会对以/system/作为前缀的资源路径做拦截<br/>

完成一个Filter的入门案例:

步骤:定义一个包(cn.wolfcode.01.hello),在包中定义一个类(HelloFilter),然后让这个HelloFilter实现接口Filter,实现接口中的三个方法(init(),doFilter(),destroy()),然后在web.xml文件中进行注册。

注意在导入jar的时候是导入的javax.servlet.Filter


5.png
public class HelloFilter implements Filter {

    public HelloFilter(){
        System.out.println("constructor method");
    }

    @Override
    public void init(FilterConfig filterConfig)  {
        System.out.println("init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Hello Filter");
    }

    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}
<filter>
    <filter-name>HelloFilter</filter-name>
    <filter-class>cn.wolfcode.filter01.HelloFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HelloFilter</filter-name>
    <!--表示拦截所有请求-->
    <url-pattern>/*</url-pattern>
</filter-mapping>

通过打印日志得出结论:在启动Tomcat的时候,创建Filter的实例对象,并调用init方法进行初始化操作。

Filter的细节

细节一:过滤连

在上述入门案例中,无论在地址栏中输入什么都是打印了doFilter方法中的日志信息,就没有访问到真正的资源。这是为何呢?是因为没有做放行操作,就像我们举的例子中,带着月饼打算回河北,但是遇到了土匪,不但把月饼扣下,连人带车也扣下,没有放行,那么就无法回到河北,在程序中也是一样,如果在过滤器中不放行,那么是访问不到资源的。那么在过滤器中如何来放行呢?通过FilterChain(过滤连)这个对象调用doFilter方法进行放行
如:filterChain.doFilter(servletRequest,servletResponse);

细节二:过滤器的生命周期

为何要研究Filter的生命周期呢?

生命周期顾名思义就是Filter对象实例,何时创建,何时初始化,何时销毁。因为Filter对象的创建交给了Servlet容器进行管理。不需要自己去创建,我们研究它的生命周期目的就是为了在指定的时期做相应的业务逻辑。

那我们学习Filter的生命周期该如何下手呢?这个可以参照我们之前学习的Servlet技术。和Servlet的生命周期做一个对比,这样更能理解并加深记忆。

Servlet的生命周期我们都知道,当第一次发送请求的时候,Servlet对象实例创建。并执行init()方法,后面的每一次请求服务器都会开启一个新的线程并执行一次service方法,当服务器正常关闭的时候或者在Servlet对象被销毁的时候 会执行destroy()方法 (注意:Servlet对象只会创建一次,init方法也只会执行一次,是一个单例)

Servlet: 执行构造方法【1,第一次访问时】 -> 执行初始化方法【1,第一次访问】 -> 执行核心方法【n,每次访问】 ->销毁【正常关闭Tomcat】

Filter的生命周期和Servlet差不多,当服务器启动的时候,Filter对象实例会被创建,并调用init方法进行初始化操作。如果拦截的资源那里配置的是/*,那么后面的每次发送请求,和服务器给出的响应都经过过滤器,都会执行doFilter()方法,当web容器正常退出时,或者Filter对象销毁的时候,会执行destory()方法.
(注意:filter对象只会创建一次,init方法也只会执行一次,是一个单例)
注意: 当servlet和filter对象同时销毁的时候, servlet实例先销毁,再销毁filter实例

Filter: 执行构造方法【1,tomcat启动时】 -> 执行初始化方法【1,tomcat启动时】 -> 执行核心方法【n,每次访问】 ->销毁【正常关闭Tomcat】

细节三:当有多个过滤器的时候,它们之间的执行顺序是怎么样的?

接下来我们做个实验,来验证多个过滤器执行的顺序和什么是有关系的。

实验的思路如下:

1.定义三个过滤器分别为:AFilter,BFilter,CFilter

2.在web.xml文件中进行配置的时候,把filter标签的配置循序为AFilter,BFilter,CFilter,把filter-mapping标签的配置循序为
CFilter,BFilter,AFilter,通过执行代码,如果打印的顺序为
AFilter->BFilter->CFilter表示是按照filter标签的配置循序为依据,如果打印的顺序为CFilter->BFilter->AFilter表示是按照filter-mapping标签的配置顺序为依据。

代码如下:

AFilter:

public class AFilter implements Filter {
@Override
public void destroy() {}

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

@Override
public void init(FilterConfig config) throws ServletException {}

}

BFilter:

 public class BFilter implements Filter {
 @Override
 public void destroy() {}

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

 @Override
 public void init(FilterConfig config) throws ServletException {}
}

CFilter:

public class CFilter implements Filter {
 @Override
 public void destroy() {}

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

 @Override
 public void init(FilterConfig config) throws ServletException {}
}

web.xml:

 <filter>
       <filter-name>AFilter</filter-name>
       <filter-class>cn.wolfcode.filter02.AFilter</filter-class>
   </filter>

   <filter>
       <filter-name>BFilter</filter-name>
       <filter-class>cn.wolfcode.filter02.BFilter</filter-class>
   </filter>

   <filter>
       <filter-name>CFilter</filter-name>
       <filter-class>cn.wolfcode.filter02.CFilter</filter-class>
   </filter> 
   
   <filter-mapping>
       <filter-name>CFilter</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>


   <filter-mapping>
       <filter-name>BFilter</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>


   <filter-mapping>
       <filter-name>AFilter</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>      

运行结果:


7.png

细节四:使用注解的方式

一个拦截器只负责处理拦截一个功能,那么如果需要完成多个功能的拦截,就要写多个拦截器,每一个拦截器都在web.xml文件中进行注册,会导致web.xml文件显得很臃肿,加载读取时,需要很长时间。那么当初在学习Servlet的时候,我们是使用了注解来解决此类的问题。那么过滤器可不可以也是使用注解呢?答案是可以的。Filter和Servlet很相似,Servlet使用的是@WebServlet("/资源名称")标签。Filter 使用@WebFilter("/拦截的资源路径")标签,来注册过滤器。

注意:使用了注解的方式来注册Filter,如果有多个Filter,执行顺序是不固定的

代码如下:

@WebFilter("/*")
public class FilterDemoB implements Filter {
 @Override
 public void destroy() {}

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

 @Override
 public void init(FilterConfig config) throws ServletException {}
}

@WebFilter("/*")
public class FilterDemoC implements Filter {
 @Override
 public void destroy() {}

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

 @Override
 public void init(FilterConfig config) throws ServletException {}
}

@WebFilter("/*")
public class FilterDemoD implements Filter {
 @Override
 public void destroy() {}

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

 @Override
 public void init(FilterConfig config) throws ServletException {}
}

运行结果:

8.png
细节五:请求过滤的类型

通过演示一个问题引出我们的讲的过滤的类型。接下来要实现一个案例:
定义一个过滤器(TestFilter),定一个两个Servlet(AServlet,BServlet),有AServlet通过请求转发跳转到BServlet中。

代码如下:

AServlet:

@WebServlet("/a")
public class AServlet extends HttpServlet {

  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {

    System.out.println("a Servlet");
    req.getRequestDispatcher("/b").forward(req,resp);
  }
}

BServlet:

@WebServlet("/b")
public class BServlet extends HttpServlet {

  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    System.out.println("b servlet");
    resp.setContentType("text/html;charset=utf-8");
    resp.getWriter().print("这是BServlet");
  }
}

TestFilter:

public class TestFilter implements Filter {
  @Override
  public void destroy() {}

  @Override
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
      throws ServletException, IOException {
    System.out.println("进入过滤器");
    chain.doFilter(req, resp);
  }

  @Override
  public void init(FilterConfig config) throws ServletException {}
}
<filter>
    <filter-name>TestFilter</filter-name>
    <filter-class>cn.wolfcode.filter04.TestFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>TestFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

通过测试发现请求转发,只经过了一次过滤器。这是为何呢?详细分析看下面的图解


9.png

通过实验证明过滤器只对请求过滤。所以对于请求转发的操作,就过滤不了。
那需要对请求转发的情况也要过滤怎么办呢?这时候需要修改请求的类型了。
默认类型是REQUEST,需要增加一个FORWARD类型,如何修改呢?通过在配置文件中添加dispatcher标签。

代码如下:

<filter>
   <filter-name>TestFilter</filter-name>
   <filter-class>cn.wolfcode.filter04.TestFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>TestFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

可能我们有个疑问:默认类型是REQUEST,为了解决请求转发也走过滤器为何是增加FORWARD而不是修改呢?

注意:这里默认类型是REQUEST,如果修改为FORWARD,那么最终导致只对请求转发才有过滤了,默认的REQUEST就被覆盖了,就像我们java中,一个类中有个默认构造器,如果你显示的写一个构造器,那么默认的构造器就没有了。所以是添加一个标签而不是修改。

Filter的应用场景

案例一:请求编码过滤器:CharacterEncodingFilter

案例的需求:

定义一个Servlet (LoginServlet),定义两个Filter(CharacterEncodingFilter1,CharacterEncodingFilter2),定义一个登陆页面(login.jsp)跳转流程通过点击login.jsp的按钮跳转到LoginServlet,把页面上的登陆信息传递到LoginServlet中。经过两个过滤器CharacterEncodingFilter1和CharacterEncodingFilter2,为了案例的需要对于过滤器使用配置文件的方式进行注册,为了保证先经过Filter1然后经过Filter2,循序不会乱。

流程图如下:


10.png

比如在filter2中设置编码为utf-8,那么在LoginServlet中无需在设置编码。

代码如下:

login.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>登录页面</title>
</head>
<body>
<form action="/login" method="post">
   账号: <input type="text" name="name"><br/>
   密码:<input type="password" name="pwd"><br/>
   <input type="submit" value="登录">
</form>
</body>
</html>

LoginServlet:

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

 @Override
 protected void service(HttpServletRequest req, HttpServletResponse resp)
     throws ServletException, IOException {

   String name = req.getParameter("name");
   String pwd = req.getParameter("pwd");
   System.out.println("name = " + name);
   System.out.println("pwd = " + pwd);
 }
}

CharacterEncodingFilter1:

public class CharacterEncodingFilter1 implements Filter {
  @Override
  public void destroy() {}

  @Override
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
      throws ServletException, IOException {
    chain.doFilter(req, resp);
  }

  @Override
  public void init(FilterConfig config) throws ServletException {}
}

CharacterEncodingFilter2:

public class CharacterEncodingFilter2 implements Filter {
  @Override
  public void destroy() {}

  @Override
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
      throws ServletException, IOException {
    // 设置post请求的编码
    req.setCharacterEncoding("utf-8");
    chain.doFilter(req, resp);
  }

  @Override
  public void init(FilterConfig config) throws ServletException {}
}

web.xml:

<filter>
  <filter-name>CharacterEncodingFilter1</filter-name>
  <filter-class>cn.wolfcode.filter05.CharacterEncodingFilter1</filter-class>
</filter>
 
<filter-mapping>
   <filter-name>CharacterEncodingFilter1</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
  <filter-name>CharacterEncodingFilter2</filter-name>
  <filter-class>cn.wolfcode.filter05.CharacterEncodingFilter2</filter-class>
</filter>
<filter-mapping>
  <filter-name>CharacterEncodingFilter2</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

代码写完了,测试也没有问题,但是仔细观察代码中是有不足的地方。

问题一:硬编码问题

设置的编码,写死在了过滤器中,这个问题是硬编码的问题。如何解决硬编码问题呢?在我们之前的学习课程中有所接触,会让我们立刻想到使用配置文件进行抽取。配置文件 常用的两大类: 一个是xml,一个是properties。目前就这样的应用场景而言,xml已经存在了,所以没必要在多写一个properties文件,在xml文件中进行配置即可。配置参数用init-param标签,使用子标签param-name配置参数的key,使用子标签param-value配置参数的value,如:

<filter>
   <filter-name>CharacterEncodingFilter2</filter-name>
   <filter-class>cn.wolfcode.filter05.CharacterEncodingFilter2</filter-class>
   <init-param>
     <param-name>encoding</param-name>
     <param-value>utf-8</param-value>
   </init-param>
</filter>
<filter-mapping>
  <filter-name>CharacterEncodingFilter2</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

配置好以后,只需要在对应的filter中获取即可,那么如何获取呢?这个配置的方式和我们当初学习Servlet是一样的,当初学习Servlet的时候,配置在servlet中,只针对当前的Servlet有效,所有的配置信息都封装到了ServletConfig对象中,那么同理,配置在filter中,只针对当前filter有效,filter的所有配置信息都封装到了FilterConfig对象中,要获取配置信息只需要从FilterConfig对象中拿就可以了。

代码如下:

public class CharacterEncodingFilter2 implements Filter {

  // 定义一个成员变量接收编码信息
  private String encoding;

  @Override
  public void destroy() {}

  @Override
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
      throws ServletException, IOException {
    // 设置post请求的编码
    if (encoding != null) {
      req.setCharacterEncoding(encoding);
    }
    chain.doFilter(req, resp);
  }

  @Override
  public void init(FilterConfig config) throws ServletException {
    // 获取配置文件中的编码信息
    encoding = config.getInitParameter("encoding");
  }
}

问题二:给设置编码添加一个开关,使其更为灵活

通过上述的问题一,解决了代码存在的硬编码问题,但是程序还不是很优。为何呢?目前这样的设置,决定了该过滤器必须设置编码,如果有另外一个过滤器,比如filter1,在过滤器中设置了编码,那么是不是两个过滤器都设置了编码重复了呢?如果不想在filter2中设置编码了,那么怎么解决呢?我们可以在增加一个配置,配置一个开关,有外部控制,编码是否强制设置,

代码如下:

web.xml:

<filter>
   <filter-name>CharacterEncodingFilter2</filter-name>
   <filter-class>cn.wolfcode.filter05.CharacterEncodingFilter2</filter-class>
   <!--设置编码-->
   <init-param>
       <param-name>encoding</param-name>
       <param-value>utf-8</param-value>
   </init-param>
   <!--添加开关,用来控制是否设置编码-->
   <init-param>
       <param-name>force</param-name>
       <param-value>true</param-value>
   </init-param>
</filter>

CharacterEncodingFilter2:

public class CharacterEncodingFilter2 implements Filter {

// 定义一个成员变量接收编码信息
private String encoding;
// 定一个boolean变量
private boolean force;

@Override
public void destroy() {}

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
    throws ServletException, IOException {
  // 设置post请求的编码
  if (hasLength(encoding)) {
     req.setCharacterEncoding(encoding);
  }
  chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {
  // 获取配置文件中的编码信息
  encoding = config.getInitParameter("encoding");
  //  获取是否强制设置编码
  force = Boolean.valueOf(config.getInitParameter("force"));
}

// 如果传入的参数不是null也不是空字符串返回true,否则返回false
private boolean hasLength(String str){
  return  str != null && str.trim().length() !=0;
}
}

这样添加一个开关,就可以控制是否强制设置编码了,但是有一种情况我们需要考虑,如果开关设置成了false,而在filter1中也没有进行编码设置,那么问题就变成了没有过滤器设置编码了,所以针对这种情况,我们应该修改之前的逻辑,把这种情况考虑进来。所以综合分两种情况:

1.如果开关是true,表示编码一定会设置。

2.如果开关是false,表示编码不一定有设置,所以需要通过调用getCharacterEncoding方法来判断之前有没有设置过编码,如果没有设置编码,此时需要设置编码。

综上所述:需要设置编码的情况 有两种 一种是force 为true,一种是req. getCharacterEncoding()为空 ,只要一种情况满足,都需要设置编码。

代码修改如下:

public class CharacterEncodingFilter2 implements Filter {

// 定义一个成员变量接收编码信息
private String encoding;
// 定一个boolean变量
private boolean force;

@Override
public void destroy() {}

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
    throws ServletException, IOException {
  // 设置post请求的编码
  if (hasLength(encoding)) {
    // 如果开关为true或者之前没有设置过编码
    if (force || !hasLength(req.getCharacterEncoding())) {
      req.setCharacterEncoding(encoding);
    }
  }
  chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {
  // 获取配置文件中的编码信息
  encoding = config.getInitParameter("encoding");
  //  获取是否强制设置编码
  force = Boolean.valueOf(config.getInitParameter("force"));
}

// 如果传入的参数不是null也不是空字符串返回true,否则返回false
private boolean hasLength(String str){
  return  str != null && str.trim().length() !=0;
}
}

案例二:登陆检查过滤器

案例需求:

定义一个登陆界面login.jsp (提供三个控件:账号,密码,登陆按钮), 定义一个处理登陆功能的LoginServlet,用来处理接收登陆页面传过来的账号和密码信息。定义一个展示商品列表的ProductServlet,用来显示商品列表,定义一个过滤器,在访问商品列表时进行校验。

执行流程:用户点击登陆按钮,把账号和密码信息传递给LoginServlet,在LoginServlet的service方法中判断账号和密码是否正确,如果正确把账户信息存入session中并重定向到ProductServlet中,如果不正确,重定向到登陆页面。如果用户直接访问商品列表,则先经过过滤器,那么在过滤器中判断session中有没有用户信息,如果有则放行,如果没有则进行拦截并重定向到登陆页面

流程图如下:


11.png

代码如下:

login.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
   <title>登录页面</title>
</head>
<body>
<form action="/login" method="post">
   账号: <input type="text" name="name" value="admin"><br/>
   密码:<input type="password" name="pwd"><br/>
   <input type="submit" value="登录">
</form>
</body>
</html>

LoginServlet:

 @WebServlet("/login")
public class LoginServlet extends HttpServlet {

  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {

    String name = req.getParameter("name");
    String pwd = req.getParameter("pwd");

    if(!hasLength(name) || !hasLength(pwd)){
        // 用户名或者密码为空,则重定向到登陆页面
        resp.sendRedirect("/login.jsp");
        return;
    }

    if ("admin".equals(name) && "123".equals(pwd)) {
      // 登陆成功,把用户信息保存到session中,并重定向到ProductServlet中
      req.getSession().setAttribute("USER_IN_SESSION", name);
      resp.sendRedirect("/product");

    } else {
      // 登陆失败,重定向到登陆页面
      resp.sendRedirect("/login.jsp");
    }
  }

    // 如果传入的参数不是null也不是空字符串返回true,否则返回false
    private boolean hasLength(String str){
        return  str != null && str.trim().length() !=0;
    }
}

CheckLoginStatusFilter:

public class CheckLoginStatusFilter implements Filter {

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

  @Override
  public void doFilter(
      ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
      throws IOException, ServletException {

    HttpServletRequest req = (HttpServletRequest) servletRequest;
    HttpServletResponse resp = (HttpServletResponse) servletResponse;
    Object userInfo = req.getSession().getAttribute("USER_IN_SESSION");
    if (userInfo == null) {
      // 没有登陆过,重定向到登陆页面
      resp.sendRedirect("/login.jsp");
      return;
    }

    filterChain.doFilter(req, resp);
  }

  @Override
  public void destroy() {}
}

ProductServlet:

@WebServlet("/product")
public class ProductServlet extends HttpServlet {

  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {

    resp.setContentType("text/html;charset=utf-8");
    resp.getWriter().print("商品列表");
  }
}

web.xml:

<filter>
  <filter-name>CheckLoginStatusFilter</filter-name>
  <filter-class>cn.wolfcode.filter06.CheckLoginStatusFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>CheckLoginStatusFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
 

代码编写完了,接下来完成测试工作:

12.png
为何出现上图这种情况呢?

我们之前定义的过滤器,配置的是/*,意味着是拦截所有请求,那么我们请求登陆页面,也进入了过滤器,在过滤器中判断session中是否有用户信息,发现没有,重定向到登陆页面,进入登陆页面,又被过滤器拦截了下来,所以导致死循环。最终出现了上述的情况。

那么登陆页面和处理登陆功能的Servlet 该不该进行拦截呢?

不应该拦截,我们试想一下,在我们生活中,比如写字楼门口的保安,他的作用是拦截没有门卡的人和没有登记备案信息的人,那么没有门卡的人进来,主动要去做登记信息,此时保安不应该进行拦截,如果拦截了,那么没有门卡的人怎么做登记,不登记信息,怎么进入大厦。在代码层面分析,如果对登陆相关的操作也进行拦截,很明显出现死循环的情况。所以应该对登陆相关的操作进行放行操作。

那么如何判断当前请求到地址是和登陆相关的操作呢?

首先确定应该在过滤器中添加相关的代码。我们可以调用API,获取到URL和URI。在这个应用场景中,我只需要查看URI即可,没有必要获取URL,拿到URI判断是否和登陆操作相关,如果属于登陆操作,则直接放行。

对过滤器进行修改,代码如下:

CheckLoginStatusFilter:

public class CheckLoginStatusFilter implements Filter {

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

 @Override
 public void doFilter(
     ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
     throws IOException, ServletException {

   HttpServletRequest req = (HttpServletRequest) servletRequest;
   HttpServletResponse resp = (HttpServletResponse) servletResponse;
   Object userInfo = req.getSession().getAttribute("USER_IN_SESSION");
   String uri = req.getRequestURI();
   if ("/login.jsp".equals(uri) || "/login".equals(uri)) {
     filterChain.doFilter(req, resp);
     return;
   }
   if (userInfo == null) {
     // 没有登陆过,重定向到登陆页面
     resp.sendRedirect("/login.jsp");
     return;
   }

   filterChain.doFilter(req, resp);
 }

 @Override
 public void destroy() {}
}

对代码中存在的硬编码进行优化

上述中的写法是存在硬编码的,对于登陆相关的操作,在里面做了放行处理,如果后面还有一些操作也不需要过滤怎么办,如果修改过滤器的代码的话,那么这也属于硬编码操作。按照我们之前的处理方式,就是把需要放行的路径配置到xml文件中。如:

web.xml:

<filter>
  <filter-name>CheckLoginStatusFilter</filter-name>
  <filter-class>cn.wolfcode.filter06.CheckLoginStatusFilter</filter-class>
  <init-param>
    <param-name>pass</param-name>
    <param-value>/login.jsp,/login</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>CheckLoginStatusFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
 

CheckLoginStatusFilter:

public class CheckLoginStatusFilter implements Filter {

  private List<String> uriList;

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    String param = filterConfig.getInitParameter("pass");
    String[] strings = param.split(",");
    uriList = Arrays.asList(strings);
  }

  @Override
  public void doFilter(
      ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
      throws IOException, ServletException {

    HttpServletRequest req = (HttpServletRequest) servletRequest;
    HttpServletResponse resp = (HttpServletResponse) servletResponse;
    Object userInfo = req.getSession().getAttribute("USER_IN_SESSION");
    String uri = req.getRequestURI();
    if (uriList.contains(uri)) {
      filterChain.doFilter(req, resp);
      return;
    }
    if (userInfo == null) {
      // 没有登陆过,重定向到登陆页面
      resp.sendRedirect("/login.jsp");
      return;
    }

    filterChain.doFilter(req, resp);
  }

  @Override
  public void destroy() {}
}

Listener(监听器)

课前引导为何要学习监听器呢?

监听器用于监听web应用中某些对象、信息的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当范围对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计在线人数和在线用户,系统加载时进行信息初始化,统计网站的访问量等等。如

13.png

什么是web监听器?

web监听器是一种Servlet中的特殊的类,它们能帮助开发者监听web中的特定事件,比如ServletContext,HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控。

监听器模型涉及以下三个对象,如下:

(1)事件:用户对组件的一个操作,称之为一个事件

(2)事件源:发生事件的组件就是事件源

(3)事件监听器(处理器):监听并负责处理事件的方法

Web中的监听器,主要用于监听作用域对象的创建,监听作用域对象属性的添加/删除/替换:

1):监听作用域对象的创建和销毁.

ServletRequestListener:监听请求对象的创建和销毁.
HttpSessionListener:监听会话对象(session)的创建和销毁.
ServletContextListener:监听应用的创建和销毁.

2):监听作用域对象的属性的添加/删除/替换.

ServletRequestAttributeListener: 监听request作用域中属性的添加/删除/替换.
HttpSessionAttributeListener:监听session作用域中属性的添加/删除/替换.
ServletContextAttributeListener:监听application作用域中属性的添加/删除/替换. 

如何编写自己的监听器?

    1. 先确定是监听作用域对象还是作用域对象的属性
    1. 自定义一个类,实现对应的接口
    1. 在web.xml文件中使用listener标签进行配置

案例:用监听器统计网站的累积访问人数

原理:每当有一个访问连接到服务器时,服务器就会创建一个session来管理会话。那么我们就可以通过统计session的数量来获得累积访问的人数。

代码如下:

CountServlet:

@WebServlet("/count")
public class LoginServlet extends HttpServlet {

  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {

    String name = req.getParameter("name");
    String pwd = req.getParameter("pwd");

    if(!hasLength(name) || !hasLength(pwd)){
        // 用户名或者密码为空,则重定向到登陆页面
        resp.sendRedirect("/login.jsp");
        return;
    }

    if ("admin".equals(name) && "123".equals(pwd)) {
      System.out.println("servlet come in ");
      // 登陆成功,把统计登陆成功到人数输出到浏览器上
      resp.setContentType("text/html;charset=utf-8");
      int count = (int) req.getSession().getServletContext().getAttribute("COUNT_IN_APPLICATION");
      resp.getWriter().print("您是第" + count + “位访问者”);
    } else {
      // 登陆失败,重定向到登陆页面
      resp.sendRedirect("/login.jsp");
    }
  }

    // 如果传入的参数不是null也不是空字符串返回true,否则返回false
    private boolean hasLength(String str){
        return  str != null && str.trim().length() !=0;
    }
}

OnLineCountListener:

public class OnLineCountListener implements HttpSessionListener {

  public int count = 0; // 记录session的数量

  @Override
  public void sessionCreated(HttpSessionEvent se) {
    System.out.println("listener come in");
    count++;
    se.getSession().getServletContext().setAttribute("COUNT_IN_APPLICATION", count);
  }

  @Override
  public void sessionDestroyed(HttpSessionEvent se) {
    count--;
    se.getSession().getServletContext().setAttribute("COUNT_IN_APPLICATION", count);
  }
}

web.xml:

<listener>
  <listener-class>cn.wolfcode.listener.OnLineCountListener</listener-class>
</listener>

相关文章

  • 第18讲.Filter&Listener

    学习摘要: 今天讲的内容web的两大组件:Filter(过滤器)和Listener(监听器),重点掌握Filter...

  • 文学回忆录.读书笔记18

    第18天: 第62讲、象征主义 第63讲、意识流 第64讲、未来主义 第...

  • 文学回忆录.读书笔记6

    第六天:第16讲、先秦诸子:孟子、庄子、荀子及其他 第17讲、魏晋文学 第18讲、谈音...

  • 【九段践行】20190508第四周作业内容#二小姐

    一、必做题 (一)内容:听《叶武滨时间管理100讲》第18、19、20讲 说明:1、第18讲【从专注做一件重要的小...

  • 梦想实践第十一日学习

    今日时间管理100讲第18 20讲复盘 第18讲:《习惯》从专注做重要的小事开始 金句:养成好的惯的秘诀就是:一次...

  • Filter&Listener

    Filter:过滤器 Listener:监听器

  • Filter&Listener

    Filter:过滤器 概念 web中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。...

  • Filter&Listener

    Filter过滤器 概念 Filter表示过滤器,是JavaWeb的三大组件之一,Servlet、Filter、L...

  • 14-DNS协议

    本人在“极客时间”上购买了一套“趣谈网络协议”的课程,本文为该课程第18讲(第18讲 DNS协议)的课程笔记。 1...

  • 笃行日志50

    1、今天学习《高效演讲必修课》第17讲,讲的是讲书的技巧:思路、心态、语言与认知。第18讲,樊老师解答了一些关于讲...

网友评论

      本文标题:第18讲.Filter&Listener

      本文链接:https://www.haomeiwen.com/subject/ckolcqtx.html