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的使用有两点需要注意:

  1. doFilter中最后需要调用FilterChain的doFilter方法,原因是这样才能调用后面的Filter。这个和FilterChain的设计原理有关,感兴趣可以看上面Filter原理的详细介绍。
  2. 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的功能。