您的位置 首页 java

我的Java Web之路38 – Servlet中的Filter

本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多( Java )码农和想成为(Java)码农的人。
 

目录

  1. 介绍
  2. 拦截
  3. 流程
  4. 职责单一
  5. 消除重复
  6. 维度
  7. 实现一个 Filter

介绍

我们使用 servlet 技术实现了一个简单的租房网平台,最后的总结中提到是否可以将重复的登录验证逻辑使用Filter来实现,答案显然是可以的。

Filter相关的接口可以参考 ,此处不再赘述。

拦截

首先,Filter从名字上翻译过来是:

它的作用实际上就是对经过它的某物执行某些动作,这些动作可以是剔除不需要的,也可以是添加一些需要的,也可以是进行某种变换等等。

从这个解释上说,Filter运用了 拦截 的思维。

拦截的思维还是比较常见的,比如代理、中介、拦截器、过滤器、装饰器等等都带有拦截的成分。

既然是拦截,那必然涉及到 在哪 拦截的问题,对于我们Servlet技术中的Filter,它是在请求到达Servlet的service()方法之前,以及service()方法执行完毕之后。即Filter已经将这两处拦截地方(暂且将前者拦截之后的处理称为Filter的 前置处理 ,将后者拦截之后的处理称为Filter的 后置处理 )合二为一了,所以Servlet API中也就没有所谓的PreFilter或者PostFilter之类的了。

所以,Servlet技术中的Filter包括了两个部分,前置处理和后置处理。它们都存在于Filter.doFilter()方法的方法体之中。当然,这两个部分都是由我们来实现的,有可能都存在,但一般情况之下都至少存在一个部分。如果都不存在,那就没有必要实现该Filter了啊。

一般情况下,前置处理处理的是请求,后置处理处理的是响应。但也并非一定如此,因为Filter.doFilter()方法是由Servlet容器调用的,Servlet容器已经把封装好的请求和响应作为该方法的参数传进来了,尽管此时响应还没有真正的响应(仅封装发送细节),所以你也可以对响应执行某些操作。

既然是拦截,那就必然涉及到 放行 的问题,即执行完Filter的前置处理之后,有时我们需要将请求/响应(Servlet容器传参进来,下同)继续交给Servlet来处理,然后再执行Filter的后置处理;而有时执行完Filter的前置处理之后,可能不希望请求/响应继续交给Servlet来处理,而是直接执行后置处理(如果有的话)。

Servlet API中提供了一个FilterChain接口(Servlet容器将配置的多个Filter生成FilterChain实例,然后也传参给Filter.doFilter()方法)来实现放行的问题。具体来说就是,如果我们在Filter.doFilter()方法体中调用了 FilterChain .doFilter()方法,那么就表示放行;否则就不放行(那如果调用多次会是什么结果,大家可以先想想再测试一下)。

实际上, FilterChain .doFilter()方法的调用也是区分Filter中前置处理和后置处理的标志。当然,现实中往往要复杂得多,比如,仅仅在某种条件下才需要放行,然后还存在一些不放行与放行都需要的处理,那这一部分就既是(不放行时)前置处理,又是(放行时)后置处理。所以,这种区分也是相对的。

综上所述,Filter的工作模式就像下面那样(箭头的方向表示请求/响应的流向):

关于拦截,还有什么样的请求才需要拦截的问题,这就是Filter与请求的映射问题。这个跟Servlet映射差不多,可以通过配置URL Pattern来匹配访问某些资源的请求。

同时,Filter的映射里面还可以配置请求分派的类型,因为请求可以通过普通的、转发、重定向、错误等方式分派给一个Servlet,所以也可以指定只拦截某种分派类型的请求。

关于Filter配置的这些内容,大家可以看看@WebFilter这个注解的源码和javadoc。如果使用部署描述符web.xml来配置,则可以利用Eclipse的自动补全功能,在此就不再赘述了。

流程

做任何事情,都可以也应该按照一定的流程来推进,这就是 流程 的思维。

不管是现实生活中到银行办某件事,到政府部门办某件事,网购,租房,买房,分步走战略,五年计划等等,还是将现实世界抽象到计算机世界的程序,也必然可以也应该按照一定的流程来推进和执行。

软件领域中的流程图、工作流、责任链、流式处理引擎、业务流程、某某链、状态图、活动图等,无一不带有流程的思维,不过细节和侧重点不太一样而已。

流程一般是由若干个步骤/节点组成,它们之间以一定顺序在满足某些条件时推进/执行,推进/执行时有的步骤/节点可能会重复,也可能不重复。

实际上,这有点像数学中的图的概念了。但我还是称之为 流程思维

体现在Servlet技术中的就是 FilterChain 这个接口了,它就是为了解决现实中往往需要按照流程来推进的需求。所以,Servlet技术中允许实现并配置多个Filter,形成一个FilterChain来拦截并推进对某个资源的访问流程,比如先要验证登录,再验证操作权限,再处理字符编解码,再统计计数,再解密,再给图片加个水印,再压缩等等。

显然,流程思维有很多好处:

  • 容易监控某个业务进行到哪个步骤;
  • 灵活易扩展,需要某个步骤就实现并挂载上,不需要就卸载它。
  • 代码复用提高,一个Filter可以挂载到多个流程上。
  • 等等

所以,进一步的,Servlet技术中的Filter的工作模式就变成这样:

只是多加了一个Filter来表示支持配置多个Filter形成链式的拦截和处理。

整个流程的每个Filter的执行与否,还是以前驱是否调用了 FilterChain .doFilter()方法。

每个Filter的前置处理和后置处理的执行是成对的(如果存在后置处理的话),并且各个Filter之间不会交叉嵌套执行,就好像XML语言或HTML语言中开始标签和结束标签是不会交叉嵌套的。

需要注意的是,一旦流程在某个Filter停止了,即在该Filter的doFilter()方法体中没有调用 FilterChain .doFilter()方法,那么表示该Filter就没有后置处理,接着还会执行上一个Filter的后置处理(如果有的话)。

最后, FilterChain .doFilter()方法并没有各个Filter的执行顺序的体现,实际上,Filter的执行顺序只能在部署描述符web.xml中通过定义的先后顺序来确定,即先定义的先执行。

职责单一

Filter的存在也是职责单一思维的体现。

一开始,或许我们并没有意识到某些地方/操作能形成某个职责,所以将它们都糅合在了一个Servlet中。

慢慢的,随着业务的发展,应用也要不断的演进,我们就会意识到某些地方/操作能形成某个职责,因而将它们抽象出一个独立的对象中。

这或许是那些高级专家与普通程序员的区别,高级专家能预见到这种问题,所以在项目一开始就可以做出好的设计决策。

发现职责的一个常用方法就是,看有没有重复的代码、重复的操作。

消除重复

消除重复 当然是Filter存在的一个理由或目的,甚至是大部分技术存在的理由或目的。

因为Filter可以拦截请求和响应,所以可以用它来实现一些通用的需求/功能,比如在我们的租房网平台中各个Servlet方法中重复的用户登录验证。

同时,一个Filter可以挂载到多个流程中,这里的流程实际上就是指对不同资源的访问流程。

重复,可以分为静态重复和动态重复。

静态重复是指某些静态物件的重复,在我们软件领域最常见的应该就是某些源代码的重复,即相同的代码出现在了多个地方。当然,还有其他方面的重复,比如配置参数、版本库等等。本质上,静态重复是动态重复导致的。

动态重复是指某个行为的重复,当然,这主要是指人的行为,比如,不断拷贝粘贴通用的源代码;不断的修改、测试、部署应用等等。这些行为应该尽量做成软件交给计算机去执行。因为计算机擅长做重复的事情且不易出错;而人只喜欢做分析做决定做判断,重复的行为会导致厌烦、出错(这样看来,那些制造业流水线上的工人们真的是很细心和很有耐性啊)。

这样看来,静态重复本质上是动态重复导致的。

重复当然是很不好的,除了上面提到的,还因为一旦某个重复的地方需要做修改,那么就要改动所有重复的地方,你并不一定能记住所有重复的地方,所以容易出错。当然,你可以使用搜索。

不过,重复也有好的地方。最常见的就是我们的备份,这也算是重复啊。比如应用的备份、数据的备份。

维度

首先,Servlet技术的设计上是规定某一个请求最终有且只能有一个Servlet来处理。

但往往有些需求/功能是 通用的 ,即多个Servlet都要使用到的,最典型的比如每个请求进入Servlet之时以及每个响应发送出去之前都需要记录日志;安全方面的认证授权(租房网平台中的登录验证就是其中一种);访问的计数统计;编码与解码;压缩与解压缩等等。

实际上,这些需求/功能并不是真正的业务,如果说真正的业务是一个维度(可以叫它为 业务维度 ),那么这些通用的需求/功能可以看作是另外一个维度(可以叫它为 通用维度 ),并且这个维度可以看作是垂直于 业务维度 的,像下图一样:

这样看的话,通用维度的功能就好像是一个切面一样跨越了多个业务,这正是面向切面编程( AOP )的思想。

不过,虽然Servlet技术中的Filter谈不上是AOP,却也是运用了维度的思维。这对于我们在判断什么时候该使用Filter还是有一定帮助的。

一句话,维度的思维就是要用不同的维度/角度/视角去看事物。

实现一个Filter

实现一个Filter与实现一个Servlet类似,同样可以使用Eclipse中的New工具,然后再添加自己的功能逻辑代码,或者把不必要的都删除。

下面是我将 中的租房网应用中的一些重复的代码抽出来,交给Filter去执行:

package houserenter.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.Servlet request ;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebFilter("/house.html")
public class MyFirstFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");

HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;

String userName = httpServletRequest.getParameter("userName");

if (userName == null || userName.isEmpty()) {
System.out.println("invalid user!");
httpServletResponse.sendRedirect("login.html");
} else {
chain.doFilter(request, response);
}
}
}
 

主要是将字符编码设置,登录验证的代码移到这里,同时将原来各个Servlet中的相应部分删除(这个比较简单,大家可以自行操作)。

然后,使用@WebFilter注解来配置拦截何种请求,这里配置的是跟HouseServlet一样的,即拦截访问有关房源资源的请求。

大家可以自行运行验证一下,应该没什么问题。

还有,大家也可以再实现并配置若干个拦截相同请求的Filter,这样就形成了FilterChain,然后添加一些打印日志的代码,看看Filter的前置处理和后置处理的执行是怎样的,甚至可以试试连续调用两次FilterChain.doFilter()方法。

总结

主要是通过Servlet中的Filter介绍了几个思维:

  • 拦截思维
  • 流程思维
  • 职责单一思维
  • 消除重复思维
  • 维度思维

关于Filter的:

  • Filter要实现Filter接口(依赖这个接口,即Servlet API会存在 代码入侵 到业务代码中);
  • Filter的配置与Servlet类似,可以在部署描述符中web.xml配置,也可以使用@WebFilter注解;
  • Filter包含前置处理和后置处理,以FilterChain.doFilter()方法的调用为标志来区分;
  • 可以多个Filter形成FilterChain;
  • FilterChain.doFilter()方法的调用就是放行的意思,即把请求/响应交给下个Filter,最终会交给Servlet;
  • Filter的前置处理和后置处理不会交叉嵌套执行;
  • Filter的执行顺序只能通过部署描述符中web.xml中定义的先后顺序来确定

文章来源:智云一二三科技

文章标题:我的Java Web之路38 – Servlet中的Filter

文章地址:https://www.zhihuclub.com/180102.shtml

关于作者: 智云科技

热门文章

发表回复

您的电子邮箱地址不会被公开。

网站地图