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

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

前言

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章