微服务时代,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
参考文章:
- Servlet Filter and Handler Interceptor- Spring boot Implementation
- Introduction To Servlet Filter and Handler Interceptor
- Spring Interceptor vs Filter 拦截器和过滤器区别