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