Java中的過濾器和攔截器有什么區(qū)別么?
今天我們不談框架,我們來說說這個(gè) Java 基礎(chǔ)中的過濾器和攔截器,為什么要談呢?其實(shí)就有粉絲會(huì)問了不起,這個(gè)過濾器和攔截器他們有什么區(qū)別呢?為什么面試官在面試的時(shí)候經(jīng)常會(huì)問到這個(gè)過濾器和攔截器的區(qū)別,以及他們分別對(duì)應(yīng)的應(yīng)用場(chǎng)景呢?
今天了不起就來說說這個(gè)過濾器和攔截器。
過濾器
過濾器 Filter
什么是過濾器
過濾器是 Servlet 的高級(jí)特性之一,是實(shí)現(xiàn) Filter 接口的 Java 類。其基本功能就是對(duì) servlet 的調(diào)用進(jìn)行干預(yù),在 Servlet 請(qǐng)求和響應(yīng)的過程中增加一些特定的功能??梢允褂眠^濾器實(shí)現(xiàn)的功能有:統(tǒng)一編碼,URL級(jí)別的權(quán)限訪問控制,過濾敏感詞匯,壓縮請(qǐng)求信息.
我們來通過代碼來看 Filter 的執(zhí)行過程
public interface Filter {
//用于執(zhí)行過濾器的初始化工作,web容器會(huì)在web項(xiàng)目啟動(dòng)時(shí)自動(dòng)調(diào)用該方法
default void init(FilterConfig filterConfig) throws ServletException {
}
//當(dāng)請(qǐng)求和響應(yīng)被過濾器攔截后,都會(huì)交給doFilter來處理:其中兩個(gè)參數(shù)分別是被攔截request和response對(duì)象,可以使用chain的doFliter方法來放行
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
//用于釋放關(guān)閉Filter對(duì)象打開的資源,在web項(xiàng)目關(guān)閉時(shí),由web容器自動(dòng)調(diào)用該方法
default void destroy() {
}
}
在 Filter 中我們可以看到有三個(gè)方法,分別是
- init
- doFilter
- destroy
至于每個(gè)方法的作用,了不起已經(jīng)把他們的功能寫在了注釋上面,他們實(shí)際上就是三個(gè)步驟:
1.初始化
2.處理請(qǐng)求和相應(yīng)過濾,完成操作
3.最后釋放資源
而實(shí)現(xiàn)一個(gè)自定義 Filter 也是比較簡單的,我們來實(shí)現(xiàn)一個(gè)簡單的自定義的 Filter。
@Component
@Slf4j
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("初始化過濾器:" + filterConfig.getFilterName());
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 對(duì)請(qǐng)求進(jìn)行過濾操作處理
log.info("MyFilter 開始對(duì)請(qǐng)求進(jìn)去過濾操作!");
String requestURI = httpRequest.getRequestURI();
log.info("請(qǐng)求地址:" + requestURI);
// 繼續(xù)執(zhí)行下一個(gè) Filter,如果沒有其他 Filter 的話就執(zhí)行 Controller 層代碼
long startTime = System.currentTimeMillis();
chain.doFilter(request, response);
// 執(zhí)行完用戶請(qǐng)求后,回到這里對(duì) response 響應(yīng)內(nèi)容做一些處理
long endTime = System.currentTimeMillis();
log.info("請(qǐng)求處理完畢,所花費(fèi)時(shí)間為:" + (endTime - startTime) + "ms");
}
@Override
public void destroy() {
log.info("銷毀過濾器 MyFilter");
}
}
至于 Filter 的配置的話,了不起都不用解釋,現(xiàn)在幾乎沒有再去配置 xml 的方式了,都是配置類或者注解的方式。
注解方式:
@WebFilter(filterName = "myFilter",urlPatterns = {"/*"})
配置類方式:
@Configuration
public class MyFilterConfig {
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean fb = new FilterRegistrationBean();
//設(shè)置filter啟動(dòng)順序
fb.setOrder(1);
fb.setFilter(new MyFilter());
fb.addInitParameter("phone","183****5510");
//設(shè)置攔截請(qǐng)求規(guī)則,這里攔截所有請(qǐng)求
fb.addUrlPatterns("/*");
return fb;
}
}
我們看完了這個(gè)過濾器,接下來就得看看這個(gè)攔截器了。
攔截器
攔截器 Interceptor
什么是攔截器
Spring MVC 中的攔截器Interceptor類似于 ServLet 中的過濾器Filter,它主要用于攔截用戶請(qǐng)求并作出相應(yīng)的處理。例如通過攔截器可以進(jìn)行權(quán)限驗(yàn)證、記錄請(qǐng)求信息的日志、判斷用戶是否登錄等。
老規(guī)矩,直接看源代碼
public interface HandlerInterceptor {
//預(yù)處理方法,本方法在控制器方法(MyController的方法)之前執(zhí) 行,用戶的請(qǐng)求最先到達(dá)此方法,在這個(gè)方法中可以獲取請(qǐng)求的信息,驗(yàn)證請(qǐng)求是否符合要求。以驗(yàn)證用戶是否登錄,驗(yàn)證用戶是否有權(quán)限訪問某個(gè)鏈接地址(url)。如果返回true則放行,返回false則攔截
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
//后處理方法。在controller中的方法之后執(zhí)行的。能夠獲取到處理器方法的返回值 mv,可以修改mv中的數(shù)據(jù)和視圖。可以影響到最后的執(zhí)行結(jié)果。主要是對(duì)原來的執(zhí)行結(jié)果做二次修正
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
//最后執(zhí)行的方法,在頁面渲染之后執(zhí)行。在請(qǐng)求處理完成后執(zhí)行的,框架中規(guī)定是當(dāng)你的視圖處理完成后,對(duì)視圖進(jìn)行了forword。就任務(wù)請(qǐng)求處理完成。
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
其實(shí)也是三個(gè)步驟:
1.預(yù)處理
2.后處理
3.最后執(zhí)行
至于每一步的含義和內(nèi)容,了不起同樣的再注釋中表明了。
同樣的,自定義實(shí)現(xiàn)一個(gè)攔截器也很簡單:
public class MyInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(MyInterceptor.class);
private static final ThreadLocal<Long> START_THREAD_LOCAL = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String uri = request.getRequestURI();
LOGGER.info(uri + " preHandle");
Long startTime = System.currentTimeMillis(); //獲取開始時(shí)間
START_THREAD_LOCAL.set(startTime); //線程綁定變量(該數(shù)據(jù)只有當(dāng)前請(qǐng)求的線程可見)
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
String uri = request.getRequestURI();
LOGGER.info(uri + " postHandle");
Long startTime = START_THREAD_LOCAL.get();//得到線程綁定的局部變量(開始時(shí)間)
Long endTime = System.currentTimeMillis(); //2、結(jié)束時(shí)間
Long time = endTime - startTime;
LOGGER.info("http request all time: " + time + "ms");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
String uri = request.getRequestURI();
LOGGER.info(uri + " afterCompletion");
if (START_THREAD_LOCAL != null) {
START_THREAD_LOCAL.remove(); // 移除ThreadLocal中的局部變量
}
}
}
我們自定義一個(gè)獲取并返回某個(gè)靜態(tài)資源的內(nèi)容以及整個(gè)請(qǐng)求所花費(fèi)的時(shí)間攔截器,一般這個(gè)還是比較有用的,而且還可以加一個(gè)請(qǐng)求訪問的,然后來處理方法執(zhí)行時(shí)間的。
配置攔截器其實(shí)在項(xiàng)目中是非常的常見。
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
/**
* 配置攔截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(myInterceptor())
.addPathPatterns("/api/v1/**")//需要攔截的請(qǐng)求
.addPathPatterns("/api/v1/**")//需要攔截的請(qǐng)求
.excludePathPatterns("/api/debug/**")//不攔截的請(qǐng)求
.excludePathPatterns("api/page/getName");//不攔截的請(qǐng)求
}
/**
* 注入攔截器到spring容器
* @return
*/
@Bean
public MyInterceptor myInterceptor(){
return new MyInterceptor();
}
}
我們看完了過濾器和攔截器的實(shí)現(xiàn),接下來就得來看看這個(gè)過濾器和攔截器之間的區(qū)別了。
1.依賴點(diǎn)不同
- 過濾器依賴與servlet容器,而攔截器不依賴與servlet容器
- 過濾器屬于Servlet,而攔截器屬于springmvc
2.實(shí)現(xiàn)原理不同
- 過濾器是基于函數(shù)回調(diào),而攔截器是基于java的反射機(jī)制的
3.作用域不同
- 過濾器則可以對(duì)幾乎所有的請(qǐng)求起作用,而攔截器只能對(duì)action請(qǐng)求起作用
4.生命周期不同
- 在action的生命周期中,攔截器可以多次被調(diào)用,而過濾器只能在容器初始化時(shí)被調(diào)用一次
這是區(qū)別,其實(shí)二者的相同點(diǎn)也是有的,比如,攔截器和過濾器實(shí)際上都是 Spring 中 AOP 的體現(xiàn),都能實(shí)現(xiàn)一些權(quán)限和日志方面的功能。
你知道他們是什么區(qū)別了么?