过滤器、拦截器、监听器的区别与使用
一、攔截器與過濾器的區(qū)別
過濾器的配置比較簡單,直接實(shí)現(xiàn)Filter 接口即可,也可以通過@WebFilter注解實(shí)現(xiàn)對(duì)特定URL攔截,看到Filter 接口中定義了三個(gè)方法。
- init() :該方法在容器啟動(dòng)初始化過濾器時(shí)被調(diào)用,它在 Filter 的整個(gè)生命周期只會(huì)被調(diào)用一次。注意:這個(gè)方法必須執(zhí)行成功,否則過濾器會(huì)不起作用。
- doFilter() :容器中的每一次請求都會(huì)調(diào)用該方法, FilterChain 用來調(diào)用下一個(gè)過濾器 Filter。
- destroy(): 當(dāng)容器銷毀過濾器實(shí)例時(shí)調(diào)用該方法,一般在方法中銷毀或關(guān)閉資源,在過濾器 Filter 的整個(gè)生命周期也只會(huì)被調(diào)用一次
攔截器它是鏈?zhǔn)秸{(diào)用,一個(gè)應(yīng)用中可以同時(shí)存在多個(gè)攔截器Interceptor, 一個(gè)請求也可以觸發(fā)多個(gè)攔截器 ,而每個(gè)攔截器的調(diào)用會(huì)依據(jù)它的聲明順序依次執(zhí)行。首先編寫一個(gè)簡單的攔截器處理類,請求的攔截是通過HandlerInterceptor 來實(shí)現(xiàn),看到HandlerInterceptor 接口中也定義了三個(gè)方法。
- preHandle() :這個(gè)方法將在請求處理之前進(jìn)行調(diào)用。注意:如果該方法的返回值為false,將視為當(dāng)前請求結(jié)束,不僅自身的攔截器會(huì)失效,還會(huì)導(dǎo)致其他的攔截器也不再執(zhí)行。
- postHandle():只有在 preHandle()方法返回值為true 時(shí)才會(huì)執(zhí)行。會(huì)在Controller 中的方法調(diào)用之后,DispatcherServlet 返回渲染視圖之前被調(diào)用。有意思的是:postHandle() 方法被調(diào)用的順序跟 preHandle() 是相反的,先聲明的攔截器 preHandle() 方法先執(zhí)行,而postHandle()方法反而會(huì)后執(zhí)行。
- afterCompletion():只有在 preHandle()方法返回值為true 時(shí)才會(huì)執(zhí)行。在整個(gè)請求結(jié)束之后, DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行。
如下圖:
過濾器攔截器運(yùn)行先后步驟:
其中第2步,SpringMVC的機(jī)制是由DispaterServlet來分發(fā)請求給不同的Controller,其實(shí)這一步是在Servlet的service()方法中執(zhí)行的
二、何時(shí)使用攔截器?何時(shí)使用過濾器?何時(shí)使用監(jiān)聽器
- 具體使用場景
靈活性上說攔截器功能更強(qiáng)大些,Filter能做的事情,他都能做,而且可以在請求前,請求后執(zhí)行,比較靈活。Filter主要是針對(duì)URL地址做一個(gè)編碼的事情、過濾掉沒用的參數(shù)、安全校驗(yàn)(比較泛的,比如登錄不登錄之類),太細(xì)的話,還是建議用interceptor。不過還是根據(jù)不同情況選擇合適的。
三、spring boot如何使用過濾器、攔截器
兩種方式的本質(zhì)都是一樣的,都是去FilterRegistrationBean注冊自定義Filter
- 方式一:
①、先定義Filter:
import javax.servlet.*; import java.io.IOException; public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// do something 處理request 或responseSystem.out.println("filter1");// 調(diào)用filter鏈中的下一個(gè)filterfilterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {} }②、注冊自定義Filter
@Configuration public class FilterConfig {@Beanpublic FilterRegistrationBean registrationBean() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());filterRegistrationBean.addUrlPatterns("/*");return filterRegistrationBean;} }- 方式二:
①、定義攔截器:
public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {System.out.println("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {System.out.println("afterCompletion");} }②、配置攔截器:
@Configuration public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyInterceptor());} }③Controller演示:
@RestController public class UController {@GetMapping("/home")public String home(){System.out.println("home");return "myhome";} }四、過濾器攔截器完整例子
@Configuration public class InterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyInterceptor());registry.addInterceptor(new MyInterceptor2());} } @Order(1) @Component @WebFilter(filterName = "myFilter",urlPatterns = "/*") public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("MyFilter1-----過濾器的init()方法,隨著容器的啟動(dòng)進(jìn)行了初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// do something 處理request 或responseSystem.out.println("filter1");// 調(diào)用filter鏈中的下一個(gè)filterfilterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {System.out.println("MyFilter1--------過濾器的destroy()方法,隨著容器的關(guān)閉而進(jìn)行");} } @Order(2) @Component @WebFilter(filterName = "myFilter2",urlPatterns = "/*") public class MyFilter2 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("MyFilter2------過濾器的init()方法,隨著容器的啟動(dòng)進(jìn)行了初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// do something 處理request 或responseSystem.out.println("filter2");// 調(diào)用filter鏈中的下一個(gè)filterfilterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {System.out.println("MyFilter2-----過濾器的destroy()方法,隨著容器的關(guān)閉而進(jìn)行");} } @Order(3) @Component @WebFilter(filterName = "myFilter3",urlPatterns = "/*") public class MyFilter3 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("MyFilter3------過濾器的init()方法,隨著容器的啟動(dòng)進(jìn)行了初始化");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// do something 處理request 或responseSystem.out.println("filter3");// 調(diào)用filter鏈中的下一個(gè)filterfilterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {System.out.println("MyFilter3-------過濾器的destroy()方法,隨著容器的關(guān)閉而進(jìn)行");} } public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {System.out.println("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {System.out.println("afterCompletion");} } @Controller public class MyController {@ResponseBody@RequestMapping("myTest")public String test(){System.out.println("controller.method invoked");return "ok";} }order定義filter的優(yōu)先級(jí),值越小優(yōu)先級(jí)越高,因此運(yùn)行順序?yàn)?/p> filter3—doFilter—>filter2—doFilter—>filter1—doFilter—>控制器方法—>返回到filter1—>返回到filter2—>返回到filter3
注意:@Order或者接口Ordered的作用是定義Spring IOC容器中Bean的執(zhí)行順序的優(yōu)先級(jí),而不是定義Bean的加載順序,Bean的加載順序不受@Order或Ordered接口的影響。
可以看到只有執(zhí)行的時(shí)候,才會(huì)按filter1,filter2,filter3的順序執(zhí)行。同時(shí)我們注意到:先聲明的攔截器 preHandle() 方法先執(zhí)行,而postHandle()方法反而會(huì)后執(zhí)行。也即是:postHandle() 方法被調(diào)用的順序跟 preHandle() 居然是相反的。如果實(shí)際開發(fā)中嚴(yán)格要求執(zhí)行順序,那就需要特別注意這一點(diǎn)。那為什么會(huì)這樣呢? 得到答案就只能看源碼了,我們要知道controller 中所有的請求都要經(jīng)過核心組件DispatcherServlet路由,都會(huì)執(zhí)行它的 doDispatch() 方法,而攔截器postHandle()、preHandle()方法便是在其中調(diào)用的。
- doDispatch方法
看看兩個(gè)方法applyPreHandle()、applyPostHandle()具體是如何被調(diào)用的,就明白為什么postHandle()、preHandle() 執(zhí)行順序是相反的了。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)) {for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {HandlerInterceptor interceptor = interceptors[i];if(!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}}return true;} void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {HandlerInterceptor[] interceptors = this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)) {for(int i = interceptors.length - 1; i >= 0; --i) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}}發(fā)現(xiàn)兩個(gè)方法中在調(diào)用攔截器數(shù)組 HandlerInterceptor[] 時(shí),循環(huán)的順序竟然是相反的。。。,導(dǎo)致postHandle()、preHandle() 方法執(zhí)行的順序相反。
五、監(jiān)聽器
Java Web開發(fā)中的監(jiān)聽器(listener)就是由application、session、request三個(gè)對(duì)象創(chuàng)建、銷毀或者往其中添加、修改、刪除屬性時(shí)自動(dòng)執(zhí)行代碼的功能組件,如下所示:
- ServletContextListener:對(duì)Servlet上下文的創(chuàng)建和銷毀進(jìn)行監(jiān)聽。
- ServletContextAttributeListener:監(jiān)聽Servlet上下文屬性的添加、刪除和修改。
- HttpSessionListener:對(duì)Session的創(chuàng)建和銷毀進(jìn)行監(jiān)聽。
- HttpSessionAttributeListener:對(duì)Session對(duì)象中屬性的添加、刪除和修改進(jìn)行監(jiān)聽。
- HttpSessionBindingListener:監(jiān)聽Http會(huì)話中對(duì)象的綁定信息。
- HttpSessionActivationListener:監(jiān)聽器監(jiān)聽Http會(huì)話的情況。
- ServletRequestListener:對(duì)請求對(duì)象的初始化和銷毀進(jìn)行監(jiān)聽。
- ServletRequestAttributeListener:對(duì)請求對(duì)象屬性的添加、刪除和修改進(jìn)行監(jiān)聽。
- 新建一個(gè)servlet處理
- 場景
很多時(shí)候當(dāng)我們完成某些業(yè)務(wù)后需要給用戶推送相關(guān)消息提醒。對(duì)于這種非核心業(yè)務(wù)功能我們可以拿出來,創(chuàng)建一個(gè)事件去異步執(zhí)行,從而實(shí)現(xiàn)核心業(yè)務(wù)和子業(yè)務(wù)的解耦。
- 實(shí)現(xiàn)
定義事件類 Event
創(chuàng)建一個(gè)類,繼承ApplicationEvent,并重寫構(gòu)造函數(shù)。ApplicationEvent是Spring提供的所有應(yīng)用程序事件擴(kuò)展類。
public class Event extends ApplicationEvent {private static final long serialVersionUID = 1L;private String msg ;private static final Logger logger=LoggerFactory.getLogger(Event.class);public Event(String msg) {super(msg);this.msg = msg;logger.info("add event success! message: {}", msg);}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;} }創(chuàng)建一個(gè)用于監(jiān)聽指定事件的類,需要實(shí)現(xiàn)ApplicationListener接口,說明它是一個(gè)應(yīng)用程序事件的監(jiān)聽類。注意這里需要加上@Component注解,將其注入Spring容器中。
@Component public class MyListener implements ApplicationListener<Event>{private static final Logger logger= LoggerFactory.getLogger(MyListener.class);@Overridepublic void onApplicationEvent(Event event) {logger.info("listener get event,sleep 2 second...");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}logger.info("event msg is:{}",event.getMsg());} }- 事件發(fā)布
事件發(fā)布很簡單,只需要使用Spring 提供的ApplicationEventPublisher來發(fā)布自定義事件
- 測試
- 異步執(zhí)行
默認(rèn)是沒有開啟異步的,我們需要手動(dòng)配置開啟異步功能,很簡單,只需要在配置類上加上@EnableAsync注解就行了,該注解用于聲明啟用Spring的異步方法執(zhí)行功能,需要和@Configuration注解一起使用,我們可以直接加在啟動(dòng)類上。然后在監(jiān)聽方法上加上@Async注解,說明當(dāng)前方法使用異步去執(zhí)行。
可以發(fā)現(xiàn)已經(jīng)實(shí)現(xiàn)了異步功能,主線程為nio-8080-exec-1,監(jiān)聽線程為 task-1。從瀏覽器反應(yīng)可以看出,接口直接返回了,并沒有等監(jiān)聽線程執(zhí)行完后才返回。
- 自定義異步線程池
用默認(rèn)的Spring線程池會(huì)有啥問題呢?SimpleAsyncTaskExecutor不是真的線程池,這個(gè)類不重用線程,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程。很有可能導(dǎo)致OOM。創(chuàng)建配置類(加上@Configuration),實(shí)現(xiàn)AsyncConfigurer接口,重寫Executor方法。這里我們可以將原先配置在啟動(dòng)類上的@EnableAsync注解放到這個(gè)類上而不再直接加在主啟動(dòng)類上。
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer {private static final Logger logger = LoggerFactory.getLogger(AsyncConfig.class);/*** 自定義異步線程池*/@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();//核心線程數(shù)taskExecutor.setCorePoolSize(2);//最大線程數(shù)taskExecutor.setMaxPoolSize(10);//隊(duì)列大小taskExecutor.setQueueCapacity(15);//線程名的前綴taskExecutor.setThreadNamePrefix("async-thread-");taskExecutor.initialize();return taskExecutor;}/*** 捕捉IllegalArgumentException異常* @return*/public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new MyAsyncExceptionHandler();}static class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler{@Overridepublic void handleUncaughtException(Throwable throwable, Method method, Object... objects) {logger.info("TASK Exception message - " + throwable.getMessage());logger.info("Method name - " + method.getName());for (Object param : objects) {logger.info("Parameter value - " + param);}}} }六、 過濾器、攔截器、監(jiān)聽器對(duì)比
參考文章
參考文章
參考文章
參考文章
參考文章
總結(jié)
以上是生活随笔為你收集整理的过滤器、拦截器、监听器的区别与使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动互联网派生app研究报告
- 下一篇: 为什么需要建设中台?