Servlet三大组件之-----Filter过滤器


1 学习目标

1. 能够说出过滤器的生命周期

2. 能够写出过滤器的映射路径

3. 能够说出过滤器的四种过滤类型

4. 能够利用FilterConfig获取过滤器的配置参数

5. 能够说出什么是过滤器链

6. 能够说出过滤器链的执行顺序

2 过滤器的入门

Java Web的三大组件 1)都需要交给web服务器运行  2)在web.xml文件中配置  )

  1. Servlet:  javax.servlet.Servlet 通过HTTP协议接收客户端的请求,并且做出响应的一个Java应用程序。

  2. Filter过滤器:javax.servlet.Filter 是一个接口,过滤请求,实现请求的拦截或者放行,并且添加新的功能。

  3. Listener 监听器:  javax.servlet.XxxListener,用来监听Web容器中各种域的事件    

2.1 过滤器的应用场景

  1、场景1: 用户表单提交参数,使用POST方法提交,编写Servlet,接收参数: request.getParameter() /getParameterValues()  如果在客户端提交汉字而不做任何处理,就出现中文乱码的问题。

    解决编码问题: request.setCharacterEncoding("utf-8");

Ø 问题:如果在项目中的每一个Servlet都加上request.setCharacterEncoding("utf-8");

代码重复,而且后期维护也不方便。能不能把这部分公共代码抽取处理,放在一个地方执行?

 

  2、场景2: 登录 -> 输入信息 -> 登录成功  -> 看到用户主页(欢迎xxx回来。。。)

    用于验证用户是否登录成功代码:

    HttpSession session = request.getSession()

    Object obj = session.getAttribute("loginInfo");

    if(obj==null){

      //没有登录标记,代表没有登录

    }else{

      ///已经登录了,继续访问此功能

    }

    如果用户不登录,直接访问用户主页,跳转到登录页面

    在其他需要登录才能访问的页面中,同样也需要加上验证用户是否登录成功代码。

        

Ø 问题: 能不能把这部分公共验证用户是否登录成功代码抽取处理,在一个地方执行?

 

  3. 结论:

    以上两种场景出现的问题,可以使用过滤器(Filter)解决!

    

2.2 过滤器的作用:

  1. 作用:

    通过Filter技术,对Web服务器管理的所有Web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,并且添加功能,从而实现一些“共同”的功能。

 

  2. 例如:

    1) 实现URL级别的权限访问控制

    2) 过滤敏感词汇

    3) 自动登录

    4) 压缩响应信息等一些功能。

    

2.3 过滤器编写步骤:

  特点:过滤器不是用户主动调用的,而是根据规则自己执行

  

  1. 编写一个java类,实现Filter接口,并实现其中的所有方法

  2. web.xml文件中配置Filter

    <!-- 过滤器配置 -->

    <filter>

      <!-- 内部名称 -->

      <filter-name>HelloFilter</filter-name>

      <!-- 类全名:包+简单类名 -->

      <filter-class>org.newboy.filter.HelloFilter</filter-class>

    </filter>

    <!-- 过滤器映射配置 -->

    <filter-mapping>

      <!-- 内部名称名称,和上面的名称保持一致! -->

      <filter-name>HelloFilter</filter-name>

      <!-- 需要拦截的路径 -->

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

    </filter-mapping>

  3. Filter部署到tomcat服务器运行

    

  4. 示例:

    创建一个过滤器HelloFilter,在运行HelloServlet前和后分别输出一句话,在HelloServlet中也输出一句话,观察控制台的运行效果。

    1) 过滤器类:

      public class HelloFilter implements Filter {

        ...

        @Override

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)   throws IOException, ServletException {

          System.out.println("1.请求的时候运行一次过滤器");

          chain.doFilter(request, response);

          System.out.println("3.响应的时候运行一次过滤器");

        }

        ...

      }

 

      

    2) web.xml中的配置

  <!-- 过滤器的配置 -->

      <filter>

        <!-- 过滤器的内部名字 -->

        <filter-name>hello</filter-name>

        <filter-class>org.newboy.filter.HelloFilter</filter-class>

      </filter>

      <!-- 映射路径 -->

      <filter-mapping>

        <filter-name>hello</filter-name>

        <!-- 过滤的Web资源路径 -->

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

      </filter-mapping>

 

    

    3) HelloServlet类:

      public class HelloServlet extends HttpServlet {

        public void doGet(HttpServletRequest request, HttpServletResponse response)

            throws ServletException, IOException {

          System.out.println("2.执行了目标资源:HelloServlet被执行");

        }

        public void doPost(HttpServletRequest request, HttpServletResponse response)

            throws ServletException, IOException {

          doGet(request, response);

        }

      }

      

    4) 运行结果:

      1.请求的时候运行一次过滤器

      2.运行了Web资源

      3.响应的时候运行一次过滤器

      

2.4 Filter是如何实现拦截的?

Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个Web资源进行拦截后,Web服务器每次在调用Web资源的service方法之前,都会先调用一下filterdoFilter方法,因此,在该方法内编写代码可达到如下目的:

  1) 调用目标资源之前,执行一段代码,request执行过滤任务。

  2) 是否调用目标资源(即是否让用户访问Web资源)。

    Web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则Web服务器就会调用Web资源的service方法,即Web资源就会被访问,否则Web资源不会被访问。

  3) 调用目标资源之后,执行一段代码,response执行过滤任务。

2.5 过滤器的生命周期:

  1. 过滤器加载的时机:

    过滤器在Web服务器启动的时候就加载了,因为要拦截Web资源,所以必须在所有Web资源启动之前就加载过滤器。

    

  2. 生命周期的方法:

    1). init方法:

      在创建完过滤器对象之后被调用。只执行一次

      注:过滤器在Web服务器中也是单例模式

    2). doFilter方法:

      执行过滤任务方法。执行多次。

    3). destroy方法:

      Web服务器停止或者Web应用重新加载,销毁过滤器对象。

      

  3. 示例:生命周期的过程

    /**

     * 第一个过滤器程序

     * @author NewBoy

     */

    public class HelloFilter implements Filter{

      /**

       * 1)构造方法

       */

      public HelloFilter(){

        System.out.println("1)Filter生命周期-构造方法");

      }

      

      /**

       * 2init初始化方法

       */

      public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("2)Filter生命周期-init方法");

      }

      /**

       * 3)过滤器执行过滤任务的方法

       */

      public void doFilter(ServletRequest request, ServletResponse response,

          FilterChain chain) throws IOException, ServletException {

        System.out.println("1.request->执行过滤任务");

        /**

         * 放行

         */

        chain.doFilter(request, response);

        System.out.println("3.response->执行完过滤任务");

      }

      /**

       * 4)销毁方法

       */

      public void destroy() {

        System.out.println("4)Filter生命周期-destory方法");

      }

    }

 

3 过滤器映射

3.1 过滤器的映射路径

  1. FilterServlet<url-pattern>的区别:

    1) Servlet中的url-pattern: 访问地址

    2) 过滤器中的url-pattern: 过滤的地址

  注:浏览器访问目标资源的路径,就算是目标地址不存在,过滤器依然会正常运行。

      

  2. url-pattern过滤匹配:

    问:在ServletURL地址有哪些写法?

Ø /*   匹配所有的地址

Ø *.后缀名    如:*.action

    

    1)  精确过滤

      /index.jsp  

      

    2)  模糊过滤

      /* 过滤所有的资源,包含jspservlet,图片

      *.jpg  过滤所有的图片

      /manager/*  过滤某个文件夹下所有的资源

        

  3. 有关匹配的要点:

    1) url-pattern要么以斜杠开头,要么以*.扩展名结尾  例如: /* *.do

    2) 不能同时出现这两种方式。例如 /*.do 是非法的

    3) 如果存在多个需要被过滤的资源,可以在一个filter-mapping中写多个url-pattern去匹配。或者一个filter对应多个filter-mapping

    如:

     <!-- 映射路径 -->

      <filter-mapping>

        <filter-name>hello</filter-name>

        <!--  过滤多个URL的资源 -->

        <url-pattern>*.do</url-pattern>

        <url-pattern>/newboy/*</url-pattern>

      </filter-mapping>

    或:

        <!--  过滤多个URL的资源 -->

      <filter-mapping>

        <filter-name>hello</filter-name>

        <url-pattern>*.do</url-pattern>

      </filter-mapping>

      <filter-mapping>

        <filter-name>hello</filter-name>

        <url-pattern>/itcast/*</url-pattern>

      </filter-mapping>

 

      

    4) 如果是动态资源servlet,可以使用servlet的访问URL,也可以使用Servlet名称

      如:

      <!-- 映射路径 -->

      <filter-mapping>

        <filter-name>hello</filter-name>

        <!-- 要过滤的Servlet的名字 -->

        <servlet-name>HelloServlet</servlet-name>

      </filter-mapping>

3.2 过滤器的过滤类型:

  1. 什么是过滤类型:声明哪种类型的请求才可以被拦截(过滤)

    Servlet2.4中一共有4种过滤类型:

      REQUESTFORWARDINCLUDEERROR

      

  2. 默认:当过滤器的访问地址精确匹配时,默认来自于直接访问的请求才可以被拦截

    <filter-mapping>

      <filter-name>hello</filter-name>

      <url-pattern>/HelloServlet</url-pattern>

      <dispatcher>REQUEST</dispatcher>

    </filter-mapping>

    这个配置与不配置是一样的效果。

  

  3. 来自于转发的请求才可以被拦截

    1). index.jsp转发到HelloServlet

      <jsp:forward page="/HelloServlet"/>

    2). 过滤器的配置

      <filter-mapping>

        <filter-name>hello</filter-name>

        <url-pattern>/HelloServlet</url-pattern>

        <dispatcher>FORWARD</dispatcher>

      </filter-mapping>

  

  4. 来自于包含的请求才可以被拦截

    1). 页面上使用包含动作,包含HelloServlet

      <!-- 包含HelloServlet -->

      <jsp:include page="/HelloServlet"/>

    2). 过滤器的配置

      <filter-mapping>

        <filter-name>hello</filter-name>

        <url-pattern>/HelloServlet</url-pattern>

        <dispatcher>INCLUDE</dispatcher>

      </filter-mapping>

  

  5. 来自于错误的请求才可以被拦截

    1). index.jsp中产生一个异常,如:100/0

        <% int num = 100 / 0;  %>

    2). web.xml中配置,错误页面为/HelloServlet

      <error-page>

        <error-code>500</error-code>

        <location>/HelloServlet</location>

      </error-page>

    3). 过滤器的配置

      <filter-mapping>

        <filter-name>hello</filter-name>

        <url-pattern>/HelloServlet</url-pattern>

        <dispatcher>ERROR</dispatcher>

      </filter-mapping>

      

  6. 以上的过滤类型可以同时写多个

    <filter-mapping>

      <filter-name>hello</filter-name>

      <url-pattern>/HelloServlet</url-pattern>

      <dispatcher>REQUEST</dispatcher>

      <dispatcher>FORWARD</dispatcher>

    </filter-mapping>

3.3 FilterConfig对象

  类似于ServletConfig:得到Servlet中的一些配置信息

  如:得到一个参数名为name的配置参数:

    getInitParameter("name")

  

  1. 什么是FilterConfig对象:

    过滤器配置对象,用于加载过滤器的参数配置

  2. web.xml文件中配置

    <!-- 过滤器配置 -->

    <filter>

      <!-- 内部名称 -->

      <filter-name>HelloFilter</filter-name>

      <!-- 类全名:包+简单类名 -->

      <filter-class>gz.itcast.a_hello.HelloFilter</filter-class>

      <init-param>

        <param-name>AAA</param-name>

        <param-value>AAA'value</param-value>

      </init-param>

      <init-param>

        <param-name>BBB</param-name>

        <param-value>BBB'value</param-value>

      </init-param>

    </filter>

    

  3. 在过滤器器中使用

    public void init(FilterConfig filterConfig) throws ServletException {

      System.out.println("2)Filter生命周期-init方法");

      /**

       * 通过FilterConfig对象得到参数配置信息

       */

      //得到一个参数

      System.out.println(filterConfig.getInitParameter("AAA"));

      

      Enumeration<String> enums = filterConfig.getInitParameterNames();

      //遍历所有参数

      while(enums.hasMoreElements()){

        String paramName = enums.nextElement();

        String paramValue = filterConfig.getInitParameter(paramName);

        System.out.println(paramName+"="+paramValue);

      }

    }

3.4 练习一:POST解码

编写过滤器,过滤所有Servlet中使用POST方法提交的汉字的编码。

1. 需求:

1) 2Servlet,一个是LoginServlet登录,一个是RegisterServlet注册

2) 2JSP页面,1个是login.jsp,有表单,登录名。1register.jsp,有表单,登录名。都使用POST提交 用户名使用汉字提交

3) 使用过滤器,对所有的ServletPOST方法进行过滤。

4) 过滤的编码参数,通过filterConfig得到

2. login.jsp

  <body>

  用户登录

<form action="login" method="post">

登录名:<input type="text" name="name"/>

<input type="submit" value="登录">

</form>

  </body>

 

3. LoginServlet

public class LoginServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html;charset=utf-8");

PrintWriter out = response.getWriter();

out.print("登录成功,欢迎您:" + request.getParameter("name"));

out.close();

}

 

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

doGet(request, response);

}

 

}

4. register.jsp

<body>

用户注册

<form action="register" method="post">

注册名:<input type="text" name="user"/>

<input type="submit" value="注册">

</form>

  </body>

  

 5. RegisterServlet.java

public class RegisterServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html;charset=utf-8");

PrintWriter out = response.getWriter();

out.print(request.getParameter("user") + ",注册成功!");

out.close();

}

 

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

doGet(request, response);

}

}

 

6. 过滤器

/**

 * 编码过滤器

 * @author NewBoy

 */

public class EncodeFilter implements Filter {

 

@Override

public void destroy() {

}

 

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

throws IOException, ServletException {

//设置编码

request.setCharacterEncoding(charset);

//放行

chain.doFilter(request, response);

}

String charset = null;    //定义成员变量

 

//得到编码的参数

@Override

public void init(FilterConfig filterConfig) throws ServletException {

charset = filterConfig.getInitParameter("charset");

}

 

}

 

7. web.xml

<!-- 编码过滤器 -->

<filter>

<filter-name>encode</filter-name>

<filter-class>com.itheima.a.filter.EncodeFilter</filter-class>

<!-- 指定编码 -->

<init-param>

<param-name>charset</param-name>

<param-value>utf-8</param-value>

</init-param>

</filter>

<!-- 过滤所有 -->

<filter-mapping>

<filter-name>encode</filter-name>

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

</filter-mapping>

 

<!-- servlet的配置 -->

 <servlet>

<servlet-name>LoginServlet</servlet-name>

<servlet-class>com.itheima.servlet.LoginServlet</servlet-class>

</servlet>

<servlet>

<servlet-name>RegisterServlet</servlet-name>

<servlet-class>com.itheima.servlet.RegisterServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>LoginServlet</servlet-name>

<url-pattern>/login</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>RegisterServlet</servlet-name>

<  url-pattern>/register</url-pattern>

</servlet-mapping>

 

 

4 过滤器链

4.1 什么是过滤器链:

  在一个Web程序中,如果存在多个过滤器过滤同一个Web资源,这些过滤器按前后顺序就组成了一个过滤器链。

 

4.2 Filter接口:

  1. Filter接口中的方法:

    doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

    1) 参数一: 请求对象ServletRequest父接口,使用的时候,传入的是HttpServletRequest

    2) 参数二: 响应对象,使用的时候,传入的是HttpServletResponse对象

    3) 参数三: 过滤器链对象,把请求传递给下一个过滤器

      

  2. FilterChain接口中的方法:

    doFilter(ServletRequest request, ServletResponse response)

      如果在过滤器中调用这个方法,则会把请求放行。把请求传递给下一个过滤器。

    

  3. 示例:创建两个过滤器,每个过滤器的请求和响应各输出一句话,观察过滤器的执行过程。

    /**

     * 第一个过滤器

     */

    public class FirstFilter implements Filter {

      public void init(FilterConfig filterConfig) throws ServletException {

      }

      public void destroy() {

      }

      /**

       * 执行过滤任务

       */

      public void doFilter(ServletRequest request, ServletResponse response,

          FilterChain chain) throws IOException, ServletException {

        System.out.println("1.执行FirstFilter的过滤任务");

        /**

         * 放行,执行下一个过滤器

         */

        chain.doFilter(request, response);

        System.out.println("5.执行完毕FirstFilter");

      }

    }      

  

    /**

    * 第二个过滤器

    */

    public class SecondFilter implements Filter {

      public void init(FilterConfig filterConfig) throws ServletException {

      }

      public void destroy() {

      }

      /**

       * 执行过滤任务

       */

      public void doFilter(ServletRequest request, ServletResponse response,

          FilterChain chain) throws IOException, ServletException {

        System.out.println("2.执行SecondFilter的过滤任务");

        /**

         * 放行。执行下一个过滤器。没有下一个过滤器,则执行目标资源

         */

        chain.doFilter(request, response);

        System.out.println("4.执行完毕SecondFilter");

      }

    }

    

    /**

      Web资源:Servlet

    */

    public class SecondServlet extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        System.out.println("3.执行SecondServlet目标资源");

        response.setContentType("text/html;charset=utf-8");

        response.getWriter().print("6.输出内容到浏览器");

      }

      public void doPost(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        doGet(request, response);

      }

    }

 

4.3 过滤器链的执行顺序:

  ● 疑问:多个过滤器是按什么样的顺序执行的?

  按在web.xmlfilterfilter-mapping配置的先后顺序来决定,哪个配置在前面,先运行哪个

    /**

     * 第一个过滤器

     */

    public class FirstFilter implements Filter {

      public void init(FilterConfig filterConfig) throws ServletException {

      }

      public void destroy() {

      }

      /**

       * 执行过滤任务

       */

      public void doFilter(ServletRequest request, ServletResponse response,

          FilterChain chain) throws IOException, ServletException {

        System.out.println("1.执行FirstFilter的过滤任务");

        /**

         * 放行,执行下一个过滤器

         */

        chain.doFilter(request, response);

        System.out.println("5.执行完毕FirstFilter");

      }

    }      

  

    /**

    * 第二个过滤器

    */

    public class SecondFilter implements Filter {

      public void init(FilterConfig filterConfig) throws ServletException {

      }

      public void destroy() {

      }

      /**

       * 执行过滤任务

       */

      public void doFilter(ServletRequest request, ServletResponse response,

          FilterChain chain) throws IOException, ServletException {

        System.out.println("2.执行SecondFilter的过滤任务");

        /**

         * 放行。执行下一个过滤器。没有下一个过滤器,则执行目标资源

         */

        chain.doFilter(request, response);

        System.out.println("4.执行完毕SecondFilter");

      }

    }

    

    /**

      Web资源:Servlet

    */

    public class SecondServlet extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        System.out.println("3.执行SecondServlet目标资源");

        response.setContentType("text/html;charset=utf-8");

        response.getWriter().print("6.输出内容到浏览器");

      }

      public void doPost(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        doGet(request, response);

      }

    }

 

 

5 装饰者模式

5.1 装饰者模式:

  1. 概念:

    Decorator Pattern。装饰者模式是在不改变原类文件,使用继承的情况下,动态地扩展一个类的功能。

    它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

 

  2. 装饰者模式的使用场景:

    在开发过程中,如果发现某个类的某个(某些)方法不满足需求(不够用),那么可以使用装饰者模式对该类进行装饰,

    增强这个类的方法。

    装饰者模式的作用:专门对类的方法进行增强!

  

  3. 装饰者模式中的各个角色:

1) 抽象角色:给出一个抽象接口,以规范准备接收附加功能的对象(即具体角色)。   

    Star    HttpServletRequest

2) 具体角色:定义一个将要接收附加功能的类。   

    BabyStrong      HttpServletRequestWrapper

3) 装饰角色:持有一个具体角色的实例,继承于抽象角色或具体角色,给具体角色添加附加功能的类。

    Decorator1, Decorator2   MyRequestWrapper

  4. 开发步骤:

    1)编写一个装饰类,需要继承被装饰类 (被装饰类不能是final

    2)在装饰类中声明一个被装饰类型的成员变量

    3)在装饰类的构造方法中,接收传入的被装饰类实例

    4)在装饰类中重写需要增强的方法

  5 示例:

    1) 明星有唱歌的行为,这是抽象角色

    2) 春哥是实现类,实现了唱歌的行为,这是具体角色

    3) 装饰类继承于春哥类,并且持有了Star的成员对象,Star对象通过构造方法传入SpringBrother

    4) 对现有的唱歌行为进行装饰,增加新的功能。

    5) 多个装饰角色可以以不同的顺序调用,产生的装饰效果不同。

    /**

     * 抽象角色:明星的行为  

     */

    public interface Star {

      /**

       * 唱歌的行为

       */

      void song();

    }

    

    /**

     *具体角色: 春哥,有唱歌的行为

     */

    public class SpringBrother implements Star {

      @Override

      public void song() {

        System.out.println("春哥:想唱就唱,要唱得漂亮");

      }

    }

    

    /**

     * 装饰角色1

     * @author NewBoy

     */

    public class StarDecorator1 extends SpringBrother {

      //持有一个具体对象的实例,注意这里是Star接口,不是SpringBrother

      Star sb;

      public StarDecorator1(Star sb) {

        this.sb = sb;

      }

      @Override

      public void song() {

        //对现有的功能增强

        System.out.println("乐队准备,音乐响起~~");

        sb.song();

      }

    }

    

    /**

     * 装饰角色2

     * @author NewBoy

     */

    public class StarDecorator2 extends SpringBrother {

      //持有一个具体对象的实例

      Star sb;

      public StarDecorator2(Star sb) {

        this.sb = sb;

      }

      @Override

      public void song() {

        System.out.println("隆重出场,主持人报幕!");

        sb.song();

      }

    }

    

    //操作类

    public class Test {

      public static void main(String[] args) {

        //没有装饰的情况

        SpringBrother s1 = new SpringBrother();

        s1.song();

        System.out.println("=============");

        //装饰1

        Star d1 = new StarDecorator1(s1);

        d1.song();

        System.out.println("=============");

        //在装饰1的基础上再装饰2

        Star d2 = new StarDecorator2(d1);

        d2.song();

      }

    }

 

    

5.2 装饰者模式与代理模式的区别:

1) 装饰者模式:关注于在一个对象上对方法的增强,并且可以多个装饰器组合使用,一般使用继承的方式实现。

2) 代理模式:关注于对对象的访问控制,对被代理的对象进行控制、改写其原有的功能。

6 案例:请求编码过滤

6.1 需求:有一个注册页面,提交表单,汉字解码出现乱码。

RegisterServlet中解决乱码的问题。要求既要能解决POST方法,又要能解决GET方法。

  public class RegisterServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

      response.setContentType("text/html;charset=utf-8");

      PrintWriter out = response.getWriter();

      // 解决POST方法

      request.setCharacterEncoding("utf-8");

      //得到用户名参数

      String name = request.getParameter("name");

      //解决GET方法,注意tomcat8.0,默认是utf-8,无需这样解码

      if ("GET".equals(request.getMethod())) {

        name = new String(name.getBytes("iso-8859-1"),"utf-8");

      }

      out.print(request.getMethod() + ",用户名是:" + name);

      out.close();

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

      doGet(request, response);

    }

  }

 

  

6.2 需求:使用过滤器实现过滤整个Web工程的乱码问题

  1. 分析思路:

    1) POST方法很容易实现,GET方法因为需要得到每一项的参数进行编码,而且一开始并不知道有哪些参数。

    2) 在过滤器中使用装饰者模式的方式来实现

    3) 要继承的代理类是哪一个呢?查API文档可知,HttpServletRequest接口的实现类是HttpServletRequestWrapper

    4) 重写request.getParameter()方法,对这个方法进行装饰,在方法中实现编码的功能。

    5) Servlet中只需调用request.getParameter()即可。

 

  2. 实现步骤:

    1) 写一个类MyRequestWrapper继承于HttpServletRequestWrapper

    2) 在类中声明一个成员变量HttpServletRequest

    3) 通过构造方法转入HttpServletRequest的对象

    4) 重写request.getParameter()方法,对汉字进行编码。

      ①. 调用原来request.getParameter() 得到未编码前的值

      ②. 判断如果是GET方法,而且值不为null,则进行编码

      ③. 返回增强以后的内容

    5) 在过滤器中首先给POST方法编码

    6) 创建装饰类对象

    7) 在过滤器链中放行,注意:这里的过滤器放行的是装饰类的对象,而不应该是原来的request对象。

    8) 在后面的Servlet中都不需要再写关于编码的代码,只需接收即可。

      因为doGet方法中注入的HttpServletRequest的实现类是自己的装饰类。

    

  3. 代码:

    ● HttpServlet的装饰类:

    //1.继承于HttpServletRequestWrapper

    class MyRequestWrapper extends HttpServletRequestWrapper {

      

      //2.在类中声明一个成员变量HttpServletRequest

      private HttpServletRequest request;

      //3.通过构造方法转入HttpServletRequest的对象

      public MyRequestWraper(HttpServletRequest request) {

        super(request);

        this.request = request;

      }

      

      //4.重写request.getParameter()方法,对汉字进行编码。

      @Override

      public String getParameter(String name) {

        //. 得到参数值

        String value =  request.getParameter(name);

        //. 判断如果是GET方法,则进行编码

        if ("GET".equals(request.getMethod()) && value!=null) {

          try {

            value = new String(value.getBytes("iso-8859-1"),"utf-8");

          } catch (UnsupportedEncodingException e) {

            e.printStackTrace();

          }

        }

        //. 返回增强以后的内容

        return value;

      }

    }  

 

    

    ● 汉字编码的过滤器类

    public class EncodingFilter implements Filter {

      @Override

      public void init(FilterConfig filterConfig) throws ServletException {

      }

      @Override

      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

          throws IOException, ServletException {

        //POST进行编码

        request.setCharacterEncoding("utf-8");

        //GET编码,先创建装饰者类

        MyRequestWraper req = new MyRequestWraper((HttpServletRequest) request);

        //在过滤器链中放行,注意:这里的过滤器放行的是装饰类的对象,而不应该是原来的request对象。

        chain.doFilter(req, response);

      }

      @Override

      public void destroy() {

      }

    }

 

    

    ● 过滤器的配置

     <filter>

      <description>编码过滤器,解码POSTGET方法汉字乱码的问题</description>

      <filter-name>encoding</filter-name>

      <filter-class>com.itheima.filter.EncodingFilter</filter-class>

    </filter>

    <filter-mapping>

      <filter-name>encoding</filter-name>

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

    </filter-mapping>

    

    ● 注册的Servlet

    public class RegisterServlet extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();

        //在后面的Servlet中都不需要再写关于编码的代码,只需接收即可。

        String name = request.getParameter("name");

        out.append(request.getMethod() + ",用户名是:" + name);

        out.close();

      }

      public void doPost(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        doGet(request, response);

      }

    }

 

 

6.3 作业:实现request.getParameterValues()方法的增强

使用装饰者模式重写String[] request.getParameterValues()方法,实现过滤多个值的参数。

 

1. 需求:

1) 注册表单上有一个复选框,爱好这一栏。爱好:游泳、旅游、上网、下棋。

复选框的名字叫hobby

2) 使用装饰者模式重写request.getParameterValues()方法,解决汉字乱码的问题

 

2. 步骤:

1) 修改上面的装饰类,重写方法:request.getParameterValues()

2) 调用原有request.getParameterValues()的方法,得到字符串数组

3) 判断是否是GET方法,而且value不为空。则使用循环,将数组中的每一个参数进行编码。

3) Servlet中得到爱好的值,并输出。

 

3. 代码:

●  装饰类中重写新的方法

@Override

public String[] getParameterValues(String name) {

//1.调用原来的request的同名方法,得到值

String [] values = request.getParameterValues(name);

//2. 判断数组是否为空,而且是GET方法

if (values!=null && "GET".equals(request.getMethod())) {

for (int i = 0; i < values.length; i++) {

//3.对每个值进行编码

try {

values[i] = new String(values[i].getBytes("iso-8859-1"),"utf-8");

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

}

}

return values;

}

●  注册Servlet类的代码:

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html;charset=utf-8");

PrintWriter out = response.getWriter();

//在后面的Servlet中都不需要再写关于编码的代码,只需接收即可。

String name = request.getParameter("name");

//得到爱好

String[] hobbies = request.getParameterValues("hobby");

out.print(request.getMethod() + ",用户名是:" + name + "<br/>");

out.print("您的爱好是:" + Arrays.toString(hobbies));

out.close();

}

7 案例:用户权限的控制

7.1 需求:

  add.jsp 添加数据,需要登录才可访问

  update.jsp 修改数据,需要登录才可访问

  list.jsp 查询数据,不用登录

  login.jsp 登录页面

  使用过滤器进行权限的控制,实现正确的访问。

 

7.2 实现步骤:

  1. WebRoot下创建4个页面

    login.jsp上使用${msg},显示信息。

  2. 创建LoginServlet

    判断用户名密码是否正确,如果正确,则在会话中保存用户信息。登录成功跳转到list.jsp,登录失败则在域中写入登录失败的信息,并且跳转到login.jsp

    

    public class LoginServlet extends HttpServlet {

      public void doGet(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        //汉字的编码

        request.setCharacterEncoding("utf-8");

        String name = request.getParameter("name");

        String password = request.getParameter("password");

        if ("admin".equals(name) && "123".equals(password)) {

          //如果登录成功,则把用户信息放入会话中

          request.getSession().setAttribute("name", name);

          //转到查询页面

          response.sendRedirect(request.getContextPath() + "/list.jsp");

        }

        else {

          request.setAttribute("msg", "登录失败");

          //转到登录页面

          request.getRequestDispatcher("/login.jsp").forward(request, response);

        }

      }

      public void doPost(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        doGet(request, response);

      }

    }    

    

    现在的状态是:所有的页面都可以直接访问,无论有没有登录。

按之前的做法是在每个页面判断会话中是否有用户信息,这种做法太繁琐。

 

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

    1) 得到HttpServletRequestHttpSession对象

    2) 如果会话中没有用户信息,则转到登录页面,并return

    3) 否则继续访问后续的Web资源

    @Override

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

        throws IOException, ServletException {

      //得到request,session对象

      HttpServletRequest req = (HttpServletRequest) request;

      HttpSession session = req.getSession();

      //判断会话中是否有已经登录的用户信息

      String name = (String) session.getAttribute("name");

      if (name==null) {

        //表示会话中没有登录信息

        req.setAttribute("msg", "你没有登录,无权访问此页面");

        request.getRequestDispatcher("/login.jsp").forward(req, resp);

        //加上return,让后面的代码不再运行

        return;

      }

      //如果已经登录了,则可以正常访问

      chain.doFilter(req, resp);}

 

 

  4. 配置web.xml中的过滤器

    ● 问:url-pattern怎么写?

      1) 方式一:写多个add.jsp

        如果页面太多,写起来很繁琐

      2) 方式二:写*.jsp

        会把login.jsplist.jsp也过滤掉,导致死循环

      这些方式都存在一定的问题

 

    ● 解决方案:

      一般按不同的用户权限创建不同的文件夹,则把需要过滤的页面放在指定的文件夹下。

      这里把add.jspmodify.jsp放入manager文件夹下。

      写成:

      <filter>

        <filter-name>authority</filter-name>

        <filter-class>com.itheima.filter.LoginAuthorityFilter</filter-class>

      </filter>

      <filter-mapping>

        <filter-name>authority</filter-name>

        <url-pattern>/manager/*</url-pattern>

      </filter-mapping>

 

    ● 注意:不要把LoginServlet的访问路径也加入过滤器了,不然会导致永远都登录不成功。

8 改造通讯录系统

8.1 案例与作业:

  需求:改造联系人管理的项目,一个模块中所有的功能使用一个Servlet来实现。即一个Servlet实现增删改查的功能。

  

8.2 为什么要改成一个Servlet来实现

  1. 每个功能使用一个Servlet来实现,一个功能模块,增删改查就会出现4Servlet,随着项目的开发,功能模块越来越多。项目中的Servlet也会越来越多,项目越大Servlet的数量就会越大,对项目的管理和维护不利。

  2. 每个Servlet在运行的时候,都会加载进内存,而且是常驻内存,一直到服务器关闭都不会释放。所以少创建一个Servlet,系统占用内存会更少。

  

8.3 实现思路:

  1. 创建一个Servlet,名为ContactServlet,这个Servlet的访问地址是:/contact

  2. 核心:给每种访问操作添加一个参数,用来区分不同的操作。

    添加联系人,则使用:/contact?action=addContact做为参数

    更新联系人,则使用:/contact?action=updateContact做为参数

    查询联系人,则使用:/contact?action=listContact做为参数

    删除联系人,则使用:/contact?action=deleteContact做为参数

    查询指定联系人,则使用:/contact?action=queryContact做为参数

  3. ContactServlet中写五个方法:

    五个方法都直接从原来相应的servlet中复制过来即可

    addContactAddContactServletdoGet方法中复制过来。并且把doGet方法改名为addContact()

    其它不变。

    /*

     * 添加联系人

     */

    public void addContact(HttpServletRequest request, HttpServletResponse response)

    /*

     * 显示所有的联系人

     */

    public void listContact(HttpServletRequest request, HttpServletResponse response)

    /*

     * 更新联系人

     */

    public void updateContact(HttpServletRequest request, HttpServletResponse response)

    /*

     * 删除联系人

     */

    public void deleteContact(HttpServletRequest request, HttpServletResponse response)

    /*

     * 查询指定的联系人

     */

    public void queryContact(HttpServletRequest request, HttpServletResponse response)

    

  4. ContactServlet中接收action的值,通过接收不同的值来调用不同的方法

    ● 注意:编码要放在所有汉字得到之前

    public void doGet(HttpServletRequest request, HttpServletResponse response)

      throws ServletException, IOException {

      //注意:编码要放在所有汉字得到之前

      request.setCharacterEncoding("utf-8");

      //1.得到用户提交的action参数

      String action = request.getParameter("action");

      //2.通过不同的参数调用不同的方法

      if ("listContact".equals(action)) {

        listContact(request, response);   //查询所有联系人

      }

      else if ("addContact".equals(action)) {

        addContact(request, response);   //添加联系人

      }

      else if ("updateContact".equals(action)) {

        updateContact(request, response);   //修改所有联系人

      }

      else if ("queryContact".equals(action)) {

        queryContact(request, response);     //查询一个所有联系人

      }

      else if ("deleteContact".equals(action)) {

        deleteContact(request, response);     //删除指定联系人

      }

    }

 

  

  5. web.xml中配置的变化:

    1) 删除其它的Servlet的配置,只保留ContactServlet的配置

    <servlet>

    <description>联系人功能模块</description>

    <servlet-name>ContactServlet</servlet-name>

    <servlet-class>com.itheima.servlet.ContactServlet</servlet-class>

    </servlet>

    <servlet-mapping>

    <servlet-name>ContactServlet</servlet-name>

    <url-pattern>/contact</url-pattern>

</servlet-mapping>

 

    2) 注意:欢迎文件列表这样写不起作用

    <welcome-file-list>

      <welcome-file>contact?action=listContact</welcome-file>

    </welcome-file-list>

可以创建一个index.jsp,在index.jsp上进行页面的转发。

<jsp:forward page="/contact?action=listContact"/>

  

  6. JSP上所有相应的访问地址都要修改

    1) 添加add.jsp的表单

      <form action="contact?action=addContact" method="post">

    2) 更新update.jsp的表单

      <form action="contact?action=updateContact" method="post">

    3) 删除delete.jsp的链接

      <a href="contact?action=deleteContact&id=xxxxxx">删除</a>

    4) 查询指定的联系人的链接

      <a href="contact?action=queryContact&id=2000">修改</a>

  

8.4 相应的代码

  1. ContactServlet.java

  public class ContactServlet extends HttpServlet {

    //1. 因为所有的方法都要调用业务类,所以把业务类声明为成员变量

    ContactService contactService = new ContactServiceImpl();

    

    public void doGet(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

      //2. 注意:解码要放在所有汉字得到之前

      request.setCharacterEncoding("utf-8");

      //3.得到用户提交的action参数

      String action = request.getParameter("action");

      //4.通过不同的参数调用不同的方法

      if ("listContact".equals(action)) {

        listContact(request, response);

      }

      else if ("addContact".equals(action)) {

        addContact(request, response);

      }

      else if ("updateContact".equals(action)) {

        updateContact(request, response);

      }

      else if ("queryContact".equals(action)) {

        queryContact(request, response);

      }

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

      doGet(request, response);

    }

    

    /*

     * 添加联系人

     */

    public void addContact(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

      String name = request.getParameter("name");

      String gender = request.getParameter("gender");

      String age = request.getParameter("age");

      String jiguan = request.getParameter("jiguan");

      String qq = request.getParameter("qq");

      String email = request.getParameter("email");

      //封装联系人

      Contact contact = new Contact(null, name, gender, Integer.parseInt(age), jiguan, qq, email);

      //调用业务类

      try {

        //正常添加

        contactService.addContact(contact);

        //重定向,可以避免重复的添加,转到显示用户列表的Servlet

        response.sendRedirect(request.getContextPath() + "/index.jsp");

      } catch (NameExistException e) {

        //把信息放在request

        request.setAttribute("msg", e.getMessage());

        request.getRequestDispatcher("/add.jsp").forward(request, response);

      }

    }

    

    /*

     * 显示所有的联系人

     */

    public void listContact(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

      //查询所有联系人

      List<Contact> contacts = contactService.findAll();

      //放到request域中

      request.setAttribute("contacts", contacts);

      //转发

      request.getRequestDispatcher("/list.jsp").forward(request, response);

    }

    

    /*

     * 查询指定的联系人

     */

    public void queryContact(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

      //1.通过ID查询一条记录

      String id = request.getParameter("id");

      //2. 通过id查询一个联系人

      Contact contact = contactService.findById(id);

      //3. 放到域

      request.setAttribute("contact", contact);

      //在域中保存一个数组

      String[] jiguans = {"广东","广西","湖南"};

      request.setAttribute("jiguans", jiguans);

      // 4. 转发

      request.getRequestDispatcher("/modify.jsp").forward(request, response);

    }

    

    /*

     * 更新联系人

     */

    public void updateContact(HttpServletRequest request, HttpServletResponse response)

        throws ServletException, IOException {

      //将表单数据封装到了Contact

      String id = request.getParameter("id");

      String name = request.getParameter("name");

      String gender = request.getParameter("gender");

      String age = request.getParameter("age");

      String jiguan = request.getParameter("jiguan");

      String qq = request.getParameter("qq");

      String email = request.getParameter("email");

      //封装联系人

      Contact contact = new Contact(id, name, gender, Integer.parseInt(age), jiguan, qq, email);

      //调用业务类

      contactService.updateContact(contact);

      //重定向

      response.sendRedirect(request.getContextPath() + "/index.jsp");

    }

  }

 

  

8.5 代码的改进

  1. 分析:

    通过不同的参数调用不同的方法,这个代码繁琐,但是又有规律。

    if ("listContact".equals(action)) {

      listContact(request, response);

    }

    else if ("addContact".equals(action)) {

      addContact(request, response);

    }

    else if ("updateContact".equals(action)) {

      updateContact(request, response);

    }

    else if ("queryContact".equals(action)) {

      queryContact(request, response);

    }

    

  2. 优化思路1:让参数选择相应的方法运行。

    1). 约定前提:参数的值与ContactServlet中方法名相同

      即:contact?action=addContact,则调用addContact()方法

    2). 得到action的值,就可以知道方法名,可以通过反射来实现方法的调用

      回顾通过反射调用方法的知识点

    

  3. 实现代码:

    ContactServlet.java中的doGet方法

    public void doGet(HttpServletRequest request, HttpServletResponse response)

    throws ServletException, IOException {

      //注意:解码要放在所有汉字得到之前

      request.setCharacterEncoding("utf-8");

      //1.得到用户提交的action参数

      String action = request.getParameter("action");

      try {

        //2.参数:通过方法名,参数列表得到方法对象

        Method actionMethod = this.getClass().getMethod(action, HttpServletRequest.class,HttpServletResponse.class);

        //3. 调用方法:

        actionMethod.invoke(this, request,response);

      } catch (Exception e) {

        e.printStackTrace();

      }

    }

 

    

  4. 优化思路2:把公共的代码放在BaseServlet

    因为所有的功能模块都是可以公用这些代码的,可以抽取成一个BaseServletBaseServlet无需在web.xml中配置

ContactServlet中只出现与Contact有关的代码,然后ContactServlet继承于BaseServlet

    /**

     * 所有Servlet的父类

     * @author NewBoy

     */

    public class BaseServlet extends HttpServlet {

      //业务类

      protected ContactService contactService = new ContactServiceImpl();

      //所有Servlet中的doGetdoPost方法都在这里

      public void doGet(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        //注意:解码要放在所有汉字得到之前

        request.setCharacterEncoding("utf-8");

        //1.得到用户提交的action参数

        String action = request.getParameter("action");

        try {

          //2.参数:通过方法名,参数列表得到方法对象

          Method actionMethod = this.getClass().getMethod(action, HttpServletRequest.class,HttpServletResponse.class);

          //3. 调用方法:

          actionMethod.invoke(this, request,response);

        } catch (Exception e) {

          e.printStackTrace();

        }

      }

      

      public void doPost(HttpServletRequest request, HttpServletResponse response)

          throws ServletException, IOException {

        doGet(request, response);

      }

    }

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值