天天看點

spring mvc 常見攔截過濾處理器Interceptor、Filter、Converter等對比

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的功能。

繼續閱讀