spring mvc 常见拦截过滤处理器Interceptor、Filter、Converter等对比
- 前言
- Filter
- 原理
- 使用
- Intercepter
- 原理
- 使用
- Converter
- 原理
- 使用
- Binder
前言
spring mvc提供了完整的服务框架,能够对web请求进行处理,包括参数解析、错误校验等。但是有些时候,开发者需要自行对请求进行预处理,比如设置一些http头、参数处理等。针对这种情况,spring mvc也提供了丰富的工具供使用。接下来我会按照整个框架的调用流程的顺序来对比介绍这几种组件。
Filter
原理
Filter是这几个中最早被调用的。根据我之前的文章,可以知道,请求的会最先由tomcat (Servlet容器)获取,在tomcat中定义了底层TCP套接字处理的整个流程。tomcat进行解析之后,会经由Wrapper容器获取Servlet(spring mvc实现的),然后将请求传递到spring mvc处理的领域中。而Filter发挥作用的地方正是在tomcat将请求传递给spring mvc之前的临界处,通过FilterChain,会依次数组中的每一个Filter来处理请求。详细原理可以参考tomcat + spring mvc原理(五):tomcat Filter组件实现原理。
使用
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
public default void destroy() {}
}
Filter的接口原型中包括了三个方法,init可以允许使用者添加自己的初始化逻辑,doFilter是用来实际处理请求的方法,destroy可以允许开发者定义所有filter被销毁时的逻辑,比如释放资源、清除内存之类。
spring mvc中使用是需要在配置文件中注册自己实现的Filter,spring boot中提供了一些注解,可以简化Filter的加载。
@WebFilter(urlPatterns = "/*", filterName = "commonRequestFilter")
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class CommonRequestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
......
chain.doFilter(httpServletRequest, httpServletResponse);
}
@Override
public void destroy() {
}
}
@WebFilter可以用来定义对哪些url的路由进行拦截,可以使用通配符。@Order用来定义所有Filter中拦截的优先级,高优先级的会先被调用。@Component就是用来注入bean了。
Filter的使用有两点需要注意:
- doFilter中最后需要调用FilterChain的doFilter方法,原因是这样才能调用后面的Filter。这个和FilterChain的设计原理有关,感兴趣可以看上面Filter原理的详细介绍。
- Filter只能处理传入的请求,而不能对请求返回的response进行处理。
Intercepter
原理
Intercepter是在请求传递到spring mvc之后发挥作用的拦截器。spring mvc框架实际上是对Servlet标准的封装,其中主要发挥作用是DispatcherServlet类。DispatcherServlet类的doDispatch方法中包含了spring mvc处理请求的主要流程,包括获取请求对应的处理器(Handler:Intercepter和用户定义的Controller)、调用处理器处理请求等逻辑。
doDispatch方法中关于Intercepter主要包括三个阶段。在调用Controller处理请求之前调用
mappedHandler.applyPreHandle(processedRequest, response)
。applyPreHandle中遍历调用了所有注册的Interceptor的preHandle方法,可以在请求进入Controler的业务处理逻辑之前对请求进行预处理。在调用Controller处理请求之后调用
mappedHandler.applyPostHandle(processedRequest, response, mv);
。applyPostHandle方法遍历调用了Interceptor的postHandle方法,可以在应答从Controller返回之后对应答进行后处理。最后一个部分是异常处理。在整理流程中,如果有任何一步抛出了错误,就会调用
mappedHandler.triggerAfterCompletion(request, response, ex);
。这个方法中会遍历调用Interceptor的afterCompletion的方法,用来定义异常出现后如何处理的逻辑。关于DispatcherServlet详细的分析可以参考tomcat + spring mvc原理(八):spring mvc对请求的处理流程。
使用
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的三个方法的被调用的时机,这样就能大致了解HandlerInterceptor的使用场景。目前使用比较多的是HandlerInterceptor的子类HandlerInterceptorAdapter,它实现了异步处理的调用方法。
public class TestInterceptor extends HandlerInterceptorAdapter {
Logger logger = LoggerFactory.getLogger(TraceIdInterceptor.class);
public TestInterceptor() {
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
......
return true;
}
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
......
}
}
使用时,首先需要继承HandlerInterceptorAdapter,在preHandle、postHandle和afterCompletion中定义相关的请求预处理、应答后处理以及异常的处理逻辑。最后需要对自己定义的Interceptor进行注册。
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor());
}
}
Converter
原理
Converter,更加明确的定位是类型转换器,是spring 框架提供的转换工具,而不只是属于spring mvc或者更简便的spring boot的组件。Converter发挥作用是在调用处理器处理请求之前的参数解析时,先获得参数类型和参数值,再根据参数类型或者参数值特征进行参数转换。
在spring mvc中,Converter的使用除了需要定义转换逻辑之外,还需要复杂的配置。在spring boot中,使用了比较简单的方式管理转换器。我在spring boot原理分析(九):上下文Context即世界2中提到了spring boot对Converter的管理。
postProcessApplicationContext方法:最后的ConverterService是会被设置的。Converter组件是用来做参数转换的,比如String到日期的转换等,这些转换器都由ConversionService管理。
具体到spring boot,这个ConversionService就是ApplicationConversionService类。ApplicationConversionService类继承自GenericConversionService,内部实现了各种Converter的注册,比如:
public static void addApplicationConverters(ConverterRegistry registry) {
addDelimitedStringConverters(registry);
registry.addConverter(new StringToDurationConverter());
registry.addConverter(new DurationToStringConverter());
registry.addConverter(new NumberToDurationConverter());
registry.addConverter(new DurationToNumberConverter());
registry.addConverter(new StringToDataSizeConverter());
registry.addConverter(new NumberToDataSizeConverter());
registry.addConverterFactory(new StringToEnumIgnoringCaseConverterFactory());
}
使用
这里只介绍spring boot的使用方式。
首先我们需要定义一个Converter
public class StringToTimeConverter implements Converter<String, Time> {
public Time convert(String value) {
Time time = null;
if (StringUtils.isNotBlank(value)) {
String strFormat = "HH:mm:ss";
int intMatches = StringUtils.countMatches(value, ":");
if (intMatches == 2) {
strFormat = "HH:mm:ss";
}
SimpleDateFormat format = new SimpleDateFormat(strFormat);
Date date = null;
try {
date = format.parse(value);
} catch (Exception e) {
e.printStackTrace();
}
time = new Time(date.getTime());
}
return time;
}
}
然后根据上面的原理可知,只需要获取ConversionService,然后将定义的Converter注册进去就可以,方法类似于Interceptor。或者还有更加简便的方法,在WebMvcAutoConfiguration文件中已经包含了自动配置的方法:
@Override
public void addFormatters(FormatterRegistry registry) {
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
这意味着,只需要继承Converter并注入bean,就会自动注册生效了。
Binder
我在tomcat + spring mvc原理(十二):spring mvc请求的适配处理和返回2中还提到了Binder这种预处理器。
spring mvc中的Binder可以对请求中输入的参数进行预处理,通过@IntiBinder注解获取Binder后能够注册一个编辑器。例如,可以在Controller中定义如下的方法:
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
binder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, true));
}
这样,这个Controller下所有GET接口传入的符合“yyyy-MM-dd HH:mm:ss”的String参数,都可以被自动转化为Date类型。spring mvc支持多种的编辑器,包括URL、URI、String、Date等。如果使用@ControllerAdvice注解,还可以定义全局或者特定包、特定类的Binder。
Binder是由spring mvc框架提供的,也能够提供类似Converter的功能。