Spring MVC中定义了对请求处理拦截的接口HandlerInterceptor,它是通过DispatcherServlet调用处理器执行链(HandlerExecutionChain)实现对请求方法调用的拦截与处理。Filter是Java Servlet的接口,实现该接口可以对Servlet请求和响应进行拦截与处理。Spring继承该接口,提供了很多常用的Filter实现类。
一、Spring MVC拦截器
HandlerInterceptor接口如下:
public interface HandlerInterceptor {
//请求处理方法执行前调用。可以用在登录验证、权限控制、参数验证和编码转换等场景
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
//请求处理方法执行之后调用,如果返回类型是视图模型,则在视图渲染之前执行。在该方法中可以设置特定的模型数据用于显示。
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
//在视图渲染之后执行,该方法中可以编写资源释放或异常记录的代码
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
HandlerInterceptor还有一个子接口AsyncHandlerInterceptor用于异步请求的拦截处理,HandlerInterceptorAdapter抽象类(处理拦截器适配器)继承自AsyncHandlerInterceptor。
DispatcherServlet的doDispatch()处理方法中,会依次调用preHandle()、控制器映射方法、postHandle()和afterCompletion()进行处理。doDispatch()方法中可以看到如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request; //所有请求的request
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//检查是否为文件上传的请求,如果是则返回的是MultipartHttpServletRequest,否则还是原来的request
processedRequest = checkMultipart(request);
//是否为文件上传请求的标志
multipartRequestParsed = (processedRequest != request);
//获取该请求的处理器链HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//1.依次调用所有前置拦截方法,applyPreHandle方法返回false时会终止该请求的处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//2.调用控制器映射方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//3.执行所有后置拦截
mappedHandler.applyPostHandle(processedRequest, response, mv);
}catch (Exception ex) {
dispatchException = ex;
}catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));
}finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
MVC拦截器配置
MVC拦截器有多种配置方式,下面是使用<mvc:interceptors>配置拦截器。
<mvc:interceptors>标签下配置拦截器Bean
<mvc:interceptors>
<!--自定义的拦截器-->
<bean class="pers.xy.bs.interceptor.PermissionInterceptor"></bean>
</mvc:interceptors>
这种配置会为每个处理器映射器(HandlerMapping)注入一个拦截器,拦截的是所有请求,包括对静态资源的请求,无论他是否被放行。
请求地址的匹配
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/**"/>
<bean class="pers.xy.bs.interceptor.PermissionInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
以上配置,PermissionInterceptor会对所有以“/user”开头的请求。
在基于Java代码注解的项目中,定义继承WebMvcConfigurationSupport类的注解配置类,通过覆写addInterceptors()方法进行拦截器的添加以及匹配路径的设置,如下:
@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PermissionInterceptor()).addPathPatterns("/user/**");
}
}
二、过滤器
Filter是Servlet 2.3开始提供的接口,其实现对Servlet请求及响应的拦截、过滤和预处理,应用场景包括:登录验证、数据压缩、加密或字符转码。过滤器的处理流程如下:
- 过滤器在Servlet处理之前拦截请求;
- 在doFilter()方法中对请求进行预处理,决定是否放行。放行后调用chain.doFilter(request,response)继续处理。也可以中断请求,转发或者重定向到其他Servlet或页面进行处理;
- Servlet处理请求并返回响应。
- 过滤器拦截响应返回(ServletResponse)并可对响应进行处理。
- 响应返回给客户端。
Spring MVC提供了一些过滤器供我们使用。
- GenericFilterBean:Spring 过滤器实现的通用抽象父类,它有如下作用:
- 实现了Filter接口,具备Filter拦截功能;
- 实现InitializingBean和DisposableBean接口,具备Bean的afterPropertiesSet()和destory()生命周期回调方法;
- 实现Aware接口,可以获取Bean的名称,环境对象和ServletContext对象。
- DelegatingFilterProxy:委派过滤器代理。如果配置了这个Bean,则其会作为所有Filter的代理,由Spring容器管理Filter的生命周期。
- ResourceUrlEncodingFilter:资源URL编码过滤器,用于将内部资源地址转换为外部地址。
- OncePerRequestFilter:继承自GenericFilterBean,OncePerRequestFilter的意思是执行一次的过滤器。该类是为了解决Filter可能被执行多次的问题。此外,该类还处理了Servlet版本兼容的问题。在Servlet2.3版本中,Filter会拦截所有请求,但在Servlet2.4及之后的版本中,Filter只会拦截外部请求,对于内部的forward和include的转发请求不会过滤。为了稳妥起见,过滤器实现从该类继承。
- AbstractRequestLoggingFilter:日志处理过滤器抽象类,包括两个子类过滤器:CommomsRequestLoggingFilter和ServletContextRequestLoggingFilter。
- CharacterEncodingFilter:字符编码过滤器,可以对POST请求中的中文字符进行转码,解决中文乱码的问题。
- CorsFilter:跨域过滤器,处理跨域访问配置。
- ForwardedHeaderFilter:转发请求头过滤器,用于获取和处理Forward请求头中的信息。
- HiddenHttpMethodFilter:HTML的Form表单仅支持GET和POST类型的方法,不支持PUT、DELETE等请求方法,但HiddenHttpMethodFilter过滤器扩展了此功能。
- MultipartFilter:文件上传过滤器。
三、过滤器与拦截器的关系
过滤器和拦截器都可以在请求处理方法执行前后进行额外的处理,两者的应用场景类似,但是属于完全不同的组件,主要区别如下:
- 技术实现:MVC Interceptor是Spring MVC的功能,Filter是Servlet的机制;
- 适用范围:MVC Interceptor可以用在Web应用和桌面应用中,Filter只能使用在Java Web项目中;
- 配置方式:MVC Interceptor配置在Spring MVC配置文件中,Filter配置在web.xml中。
- 管理方式:MVC拦截器由Spring容器管理,可以获取容器中的对象,Filter默认不由容器管理,可以配置容器进行生命周期管理,但是无法获取到容器对象。
MVC拦截器、过滤器及Spring AOP的增强和拦截器可以在项目中同时使用,逻辑结构及处理流程如下图所示: