Filter过滤器


Filter

一、概念

Javaweb三大组件(Servlet、Filter、Listener)之一,Filter就是过滤器,当访问服务器资源时,Filter可以将请求拦截下来,完成一些特定的功能,也就是过滤特定的请求资源、请求信息、响应信息;当一个请求到来,Web服务器首先判断是否有过滤器与请求资源相关联,如果有那么将请求交给过滤器处理,再由过滤器决定是否交给请求资源,响应则相反。过滤器一般用于完成通用的操作,比如:登录验证、统一编码处理、敏感字符过滤……

过滤器的基本原理:

  1. Filter就是一个实现了接口Filter的java类,与Servlet类似的由Tomcat执行
  2. 当为一个Filter配置了拦截资源,当请求该资源时,可以由该Filter决定是否放行该请求到请求的资源,以及决定是否对请求消息、响应消息作出修改
  3. 当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
  4. 但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。
  5. 只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。
  6. 如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。

二、编写Filter的步骤

  1. 定义一个类,实现接口 Filter

  2. 复写接口的方法

  3. 配置拦截路径(访问什么资源,过滤器会生效)

    @WebFilter("/*")// /*表示访问所有资源之前都会执行该过滤器,如果是/demo.jsp就是表示访问demo.jsp之前执行该过滤器
    public class FilterDemo1 implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    	// doFilter业务处理的核心代码区,相当于Servlet的service方法
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("FilterDemo1----------------------");
    
            filterChain.doFilter(servletRequest, servletResponse);// 过滤器放行请求,可以访问到index.jsp
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

三、过滤器的一些细节

1. web.xml配置

<!--先为FilterDemo1这个类配置一个过滤器的名字demo1,然后配置该过滤器应用的资源范围-->
    <filter>
        <filter-name>demo1</filter-name>
        <filter-class>cn.zhuobo.web.filter.FilterDemo1</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>demo1</filter-name>
        <url-pattern>/*</url-pattern> <!-- 这里配置的是拦截路径 -->
    </filter-mapping>

2. 过滤器的执行流程

  1. 执行过滤器

  2. 执行过滤器放行后的请求资源

  3. 执行过滤器放行后的代码

    // 对request对象做一些增强
    System.out.println("filter11111111111111111");
    
    // 放行
    chain.doFilter(req, resp);
    
    //回程,对response对象做一些增强
    System.out.println("filter222222222222222222");
    

3. 过滤器的生命周期方法

  1. init:服务器启动后创建Filter对象,调用init方法,init方法只执行一次,一般用来加载资源
  2. doFilter:每次请求被拦截的资源时都会执行,可以执行多次
  3. destroy:服务器关闭后Filter对象被销毁,如果服务器是正常关闭,就会执行destroy方法

4. 过滤器配置细节

  1. 拦截路径的配置

    1. 拦截具体资源:index.jsp,表示只有访问该资源时对应的过滤器才会被执行
    2. 拦截目录:/dir/*,表示访问dir目录下的所有资源过滤器都会被执行
    3. 拦截后缀名:*.jsp,表示访问jsp资源时过滤器会被执行
    4. 拦截全部资源:/*,表示访问所有资源都会执行过滤器
  2. 拦截方式的配置(资源访问的方式,比如浏览器直接请求,转发请求),使用dispatcherTypes属性配置,下面为该属性的5个值:

    1. DispatcherType.REQUEST:默认值,只有直接请求该资源才会执行过滤器
    2. DispatcherType.FORWARD:转发访问该资源才执行过滤器
    3. DispatcherType.INCLUDE:包含访问资源
    4. DispatcherType.ERROR:错误跳转资源
    5. DispatcherType.ASYNC:异步访问该资源

    注意dispatcherTypes的值可以是一个数组,也就是可以配置多个值,这种情况下,无论是直接访问,还是请求转发都会执行过滤器,配置了两个值

    @WebFilter(value = "/index.jsp", dispatcherTypes = {DispatcherType.FORWARD, DispatcherType.REQUEST}) // 直接访问或者转发
    

5. 过滤器链(配置多个过滤器)

可以配置多个过滤器,而且可以都生效,但是过滤器有执行顺序的问题,上一个的doFilter方法激活下一个的doFilter方法,最后一个doFilter方法激活访问Servlet的service方法,过滤器链的中间任意一个filter没有调用doFilter方法,那么最终的Servlet的service方法都不会被执行:如果有两个过滤器分别是Filter1和Filter2,那么执行顺序如下:

  1. Filter1执行
  2. Filter2执行
  3. 被请求的资源执行
  4. Filter2执行
  5. Filter1执行

还有就是过滤器的先后问题:

  1. 注解配置方式:按照类名的字符串比较顺序,值小的先执行
  2. web.xml配置方式:< filter-mapping >标签先定义者先执行

四、过滤器的应用(登录验证、敏感词过滤)

1. 登录验证

  1. 登录验证需求:

    1. 访问资源,验证是否已经登录
    2. 如果登录了就直接放行
    3. 否则就直接跳转发到登录页面,提示“请您先登录”
  2. 登录验证的过滤器的核心逻辑:

    1. 判断请求的资源是否登录相关的资源(登录页面相关的资源),如果是,就直接放行;否则就要判断是否已经登录

    2. 判断是否已经登录,因为如果登录了都会在session中存储user键,判断session中是否有user即可判断是否已经登录;

      1. 如果已经登录,直接放行
      2. 否则跳转到登录页面,提示“请您先登录”
    3. 具体代码实现:

          public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
      
              // 将req强制转换为HttpServletRequest,因为一般都是HTTP协议的
              HttpServletRequest request = (HttpServletRequest) req;
              //1. 获取访问资源的URI,就是那个很长的
              String uri = request.getRequestURI();
              // 2. 判断uri是否包含有登录相关的资源,包括login.jsp,相关的验证码、css样式、js、字体样式等
              if(uri.contains("/login.jsp") || uri.contains("/loginServlet") || uri.contains("checkCodeServlet") || uri.contains("/css/")|| uri.contains("/js/") || uri.contains("/fonts/")) {
                  // 含有登录相关的资源,表示用户就是要去登录的,直接放行
                  chain.doFilter(req, resp);
              } else {
                  // 否则就判断使用是否已经登录了
                  Object user = request.getSession().getAttribute("user");
                  if (user != null) {
                      // 已经登录的,就直接放行
                      chain.doFilter(req, resp);
                  } else {
                      // 否则就跳转到登录页面,并且提示要先登录
                      request.setAttribute("login_msg", "您还没有登录,请您先登录!");
                      request.getRequestDispatcher("/login.jsp").forward(request, resp);
                  }
              }
      
          }
      
      

2. 敏感词汇过滤

  1. 敏感词汇过滤需求:

    1. 对录入数据进行敏感词汇过滤
    2. 敏感词汇参考《敏感词汇.txt》
    3. 将敏感词替换为***
  2. 分析:

    1. 请求参数是封装在request对象的
    2. Filter过滤器doFilter的参数req对象事实上和封装请求参数的request对象时同一个
    3. 在doFilter中将请求参数中的敏感词汇替换为***,重新封装request对象
    4. 放行,chain.doFilter的参数传入重新封装的request对象
  3. 增强对象的功能,采用设计模式中的装饰模式、代理模式可增强对象的功能

    1. 装饰模式

    2. 代理模式:代理对象代理真实对象,达到增强真实对象功能的模式

      • 真实对象:被代理的对象
      • 代理对象:

      实现方式:

      • 静态代理:有一个类文件描述代理模式
      • 动态代理:在内存中形成代理
    3. 动态代理的实现步骤

      1. 代理对象和真实对象实现相同的接口(代理对象和真实对象时兄弟);
      2. 代理对象 = Proxy.newProxyInstance();
      3. 使用代理对象调动方法;
      4. 增强方法。
    4. 增强方法的方式

      1. 增强参数列表
      2. 增强返回值类型
      3. 增强方法体执行逻辑
      public class ProxyTest {
          public static void main(String[] args) {
              Huawei huawei = new Huawei();
              /**
               *Proxy.newProxyInstance方法的三个参数:
               * 1. 真实对象的类加载器:真实对象.getClass().getClassLoader()
               * 2. 接口数组:代理对象实现的接口,真实对象.getClass().getClassLoader()
               * 3. 处理器:new InvocationHandler()
               */
              SaleComputer proxy_huawei = (SaleComputer) Proxy.newProxyInstance(huawei.getClass().getClassLoader(), huawei.getClass().getInterfaces(), new InvocationHandler() {
                  /**
                   *invoke方法是代理逻辑编写的方法,代理对象调用的所有方法都会触发该方法的执行
                   *
                   * @param proxy:代理对象
                   * @param method:代理对象调用的方法,被封装成的对象
                   * @param args:代理对象调用方法时,传递的参数封装的数组
                   * @return
                   * @throws Throwable
                   */
                  @Override
                  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                      // 判断执行的方法是不是sale方法,如果是就增强参数(修改参数)
                      if("sale".equals(method.getName())) {
                          double money = (double) args[0];
                          money = money * 0.8;// 改变参数的值
                          // 使用真实对象调用方法
                          String obj = (String) method.invoke(huawei, money);
      
                          // 增强返回值
                          return obj + "、电脑包、鼠标垫......";
                      } else {// 如果不是sale方法,那么就原样执行
                          // 使用真实对象调用sale方法
                          Object obj = method.invoke(huawei, args);
                          return obj;
                      }
                  }
              });
      
              // 代理对象调用sale方法
              String computer = proxy_huawei.sale(7788);
              System.out.println(computer);
              
              proxy_huawei.show();
          }
      }
      


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM