微服务时代,java依靠SpringBoot又再度提升热度。原本以为php, python之类的会持续蚕食Java的领地,熟知微服务又恢复了Java往日的地位。SpringBoot依赖Spring生态圈满满圈粉,热度更胜当年。
SpringBoot的本质就是实现了自动装配,解决了Spring研发的配置地域问题。但是它的基础依然是Spring, 对于web研发的基础依然是SpringMVC。因此有必要深入了解Spring, SpringMVC。
对于Spring来说,拦截器和过滤器是非常核心和重要的两个概念。因此本文针对这两个概念进行深入分析,让我们彻底理解拦截器和过滤器。
关于拦截器和过滤器是什么,有大量文章已经做了说明,我们这里不详细阐述。本文分为五个部分:
- 拦截器和过滤器的区别
- 拦截器使用
- 过滤器使用
- 拦截器和过滤器的应用场景
- 拦截器和过滤器的执行过程及流程
1. 拦截器和过滤器的区别
过滤器(Filter) | 拦截器(Interceptor) | 总结 |
---|---|---|
Filter接口定义在javax.servlet包中 | HandlerInterceptor接口定义在org.springframework.web.servlet包中 | |
Filter定义在web.xml中 | HandlerInterceptor是在应用程序上下文配置的。 | |
Filter只在Servlet前后起作用。Filter通常将请求和响应当做黑盒子,Filter通常不考虑Servlet的实现。 | 拦截器能深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。允许用户接入(hook into)请求的生命周期,在请求过程中获取信息,Interceptor通常和请求更加耦合。 | 在Spring架构的程序中,要有限使用拦截器。几乎所有Filter能做的事情,Interceptor都能够轻松的实现。 |
Filter是Servlet规范规定的。 | 拦截器既可以用于Web程序,也可以用于Application、Swing程序中。 | 使用范围不同 |
Filter是在Servlet规范中定义的,是Servlet容器支持的。 | 拦截器是在Spring容器内的,是Spring框架支持的。 | 规范不同 |
Filter不能够使用Spring容器资源。 | 拦截器是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过IoC注入到拦截器即可。 | Spring中使用拦截器更容易 |
Filter是被Servlet(就像Tomcat)调用的。 | 拦截器(Interceptor)是被Spring调用的。 | 因此Filter总是优于Interceptor执行。 |
下面引用别人的一张图说明一下,在tomcat容器下,拦截器、过滤器、Servlet以及Spring Controller之间的关系。
在请求处理和响应过程中过滤器和拦截器等组件的执行流程及关系:
- 请求首先到达Servlet容器,将请求转给web容器。
- web容器调用Servlet过滤器,然后继续将请求转交给Spring的DispatcherServlet, 请求就转交给Spring上下文。
- DispatcherServlet就可以调用Spring的拦截器对请求进行拦截处理,最后交给资源控制器。
- 最终有资源控制器结合业务模型进行业务处理,然后沿着4到1一层层朝外对相应进行处理,最终返回给客户端。
2. 拦截器的使用
Interceptor的执行顺序大致为:
- 请求到达DispatcherServlet
- DispatcherServlet发送至Interceptor, 执行preHandle
- 请求到达Controller
- 请求结束后,postHandle执行
Spring中主要通过HandlerInterceptor接口来实现请求的拦截,实现HandlerInterceptor接口需要实现下面三个方法:
- preHandle(): 在handle执行之前,返回boolean值,true表示继续执行,false为停止执行并返回。
- postHandle(): 在handle执行之后,可以在返回之前对返回的结果进行修改。
- afterCompletion(): 在请求完成,视图生成后调用。
2.1 利用拦截器进行请求耗时统计
请求耗时统计可以通过在请求处理之前记录一个时间,请求处理完成后再记录一个时间,打印出两个时间的差就是请求耗时。
2.1.1 定义一个拦截器
public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter {
// private static final Logger log = LoggerFactory.getLogger(ExecuteTimeInterceptor.class);
/**
* 在handler执行之前执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return super.preHandle(request, response, handler);
}
/**
* 在handler执行之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
long startTime = (long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
System.out.println("[" + handler + "] executeTime : " + executeTime + "ms");
super.postHandle(request, response, handler, modelAndView);
}
}
2.1.2 配置拦截器
前面介绍我们知道拦截器是Spring上下文的东西,因此我们需要再Spring的上下文进行配置,使用springmvc-servlet.xml配置的方式如下:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.xxx.interceptor.ExecuteTimeInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
2.1.3 运行测试
运行起来,然后访问一个接口,就会看到控制台有输出如下:
2.2 使用拦截器实现应用安全检查
通常我们的应用分为前端应用和管理后台应用,它们的安全检查策略是不同的。我们就可以通过拦截器实现不同安全策略的检查。具体的拦截器逻辑实现我们就不列举了,仅简单描述一下:
- 对于所有前端应用的请求都进行一般的安全检查,我们暂且让FrontSecurityInterceptor拦截器来完成具体的业务。
- 对于所有后端应用的请求都进行较严格的安全检查,因为后台数据很重要。我们姑且用BackendSecurityInterceptor拦截器来完成这个艰巨的任务。
- 另外,我们前端应用和管理端应用都部署在同一个域名www.example.com下,前台应用直接访问这个链接即可,后台管理我们通过添加前缀/admin来访问。
那么我们就可以配置拦截器如下所示:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**" />
<bean class="com.yang.interceptor.FrontSecurityInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/admin/**"/>
<bean class="com.yang.interceptor.BackendSecurityInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
2.3 Spring拦截器总结
我们可以利用mvc:interceptors标签声明一系列的拦截器,然后它们就可以形成一个拦截器链,拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
在mvc:interceptors标签下声明interceptor主要有两种方式:
- 直接定义一个Interceptor实现类的bean对象。使用这种方式声明的Interceptor拦截器将会对所有的请求进行拦截。
- 使用mvc:interceptor标签进行声明。使用这种方式进行声明的Interceptor可以通过mvc:mapping子标签来定义需要进行拦截的请求路径。
3. Filter的使用
Servlet 的 Filter 接口需要实现如下方法:
void init(FilterConfig paramFilterConfig)
– 当容器初始化 Filter 时调用,该方法在 Filter 的生命周期只会被调用一次,一般在该方法中初始化一些资源,FilterConfig 是容器提供给 Filter 的初始化参数,在该方法中可以抛出 ServletException 。init 方法必须执行成功,否则 Filter 可能不起作用,出现以下两种情况时,web 容器中 Filter 可能无效:- 抛出 ServletException
- 超过 web 容器定义的执行时间。
doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain)
– Web 容器每一次请求都会调用该方法。该方法将容器的请求和响应作为参数传递进来, FilterChain 用来调用下一个 Filter。void destroy()
– 当容器销毁 Filter 实例时调用该方法,可以在方法中销毁资源,该方法在 Filter 的生命周期只会被调用一次。
Filter和Interceptor的一些用途
以下是Filter接口源代码中列举出的过滤器的一些用途
- Authentication Filters
- Logging and Auditing Filters
- Image conversion Filters
- Data compression Filters
- Encryption Filters
- Tokenizing Filters
- Filters that trigger resource access events
- XSL/T filters
- Mime-type chain Filter
4.1 请求过滤器应用场景
- 执行安全检查(perform security checks)
- 格式化请求header和body(reformat request headers or bodies)
- 审查或记录日志(audit or log requests)
- 根据请求内容授权或者限制用户访问(Authentication-Blocking requests based on user identity)
- 根据请求频率限制用户访问
4.2 响应过滤器应用场景
- 压缩响应内容,比如让下载的内容更小(Compress the response stream)
- 追加或者修改响应(append or alter the response stream)
- 创建或者整体修改响应(create a different response altogether)
- 根据地方不同修改响应内容(Localization-Targeting the request and response to a particular locale)
5. 验证过滤器和拦截器执行过程
5.1 定义两个过滤器和拦截器
下面我们验证一下过滤器和拦截器的执行流程和顺序:
- 定义两个filter, 在web.xml中进行配置: 分别是FirstFilter和SecondFilter, 对所有请求进行过滤。
- 定义两个拦截器,在spring上下文中进行配置,这里在springmvc-servlet.xml中进行配置: 分别是FirstInterceptor和SecondInterceptor。
两个Filter实现基本类似:
// Filter
public class FirstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(this.getClass().getName() + ": init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println(this.getClass().getName() + ": doFilter");
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println(this.getClass().getName() + ": destroy");
}
}
两个Interceptor的实现也基本相似:
public class FirstInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getName() + ": preHandle");
return super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getName() + ": postHandle");
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getName() + ": afterCompletion");
super.afterCompletion(request, response, handler, ex);
}
}
5.2 配置过滤器和拦截器
在web.xml配置过滤器:
<!-- 定义两个filter -->
<filter>
<filter-name>firstFilter</filter-name>
<filter-class>com.yang.filter.FirstFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>firstFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>secondFilter</filter-name>
<filter-class>com.yang.filter.SecondFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>secondFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在springmvc-servlet.xml中配置拦截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.yang.interceptor.ExecuteTimeInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.yang.interceptor.FirstInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.yang.interceptor.SecondInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
5.3 验证结果
然后运行Tomcat, 可以看到FirstFilter, SecondFilter的init()方法在启动的时候就被执行了。
我们启动tomcat后自动打开一个jsp页面: 我们看到两个filter的doFilter也执行了:
然后我们访问一个API接口: GET /demo/hello, 我们看到控制台输出:
5.4 总结
- 过滤器是被容器调用的,容器启动会调用过滤器的init()方法。
- 过滤器是针对所有请求的,包括普通的jsp页面,比如首页。
- 多个过滤器执行顺序是按照在web.xml中配置的顺序执行的。
- 拦截器是Spring上下文执行的,是在容器启动之后,请求被转给Spring的时候。
- 多个拦截器preHandle执行顺序按照在容器中配置的顺序进行执行。
- 多个拦截器afterHandle执行顺序按照在容器中配置的倒序进行执行。
- 多个拦截器afterCompletion执行顺序按照在容器中配置的倒序进行执行,并且在所有afterHandle执行完成之后执行。
码字不易,如果对大家有用,请关注 “老丐说码”。
老丐说码: 专注Java研发、SpringBoot、Spring Cloud,后面持续退出一系列相关文章。
另外推荐大家一个免费好用的本地markdown编辑器: Typora
代码地址: springmvc - springmvc-handleflow
参考文章:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。