Web生命周期內(nèi)的攔截過濾與監(jiān)聽

一.Servlet域?qū)ο笈c屬性變化監(jiān)聽

1.1 Servlet監(jiān)聽器定義

Servlet 監(jiān)聽器是 Servlet 規(guī)范中定義的一種特殊類摹蘑,用于監(jiān)聽 ServletContext剑勾、HttpSession 和 ServletRequest 等作用域?qū)ο蟮膭?chuàng)建與銷毀事件,以及監(jiān)聽這些作用域?qū)ο笾袑傩园l(fā)生修改的事件笙蒙。監(jiān)聽器使用了設(shè)計模式中的觀察者模式唯卖,它關(guān)注特定事物的創(chuàng)建黍瞧、銷毀以及變化并做出回調(diào)動作尚困,因此監(jiān)聽器具有異步的特性。
Servlet Listener 監(jiān)聽三大域?qū)ο蟮膭?chuàng)建和銷毀事件劳翰,三大對象分別是:

  1. ServletContext Listener:application 級別敦锌,整個應(yīng)用只存在一個,所有用戶使用一個ServletContext
  2. HttpSession Listener:session 級別佳簸,同一個用戶的瀏覽器開啟與關(guān)閉生命周期內(nèi)使用的是同一個session
  3. ServletRequest Listener:request 級別乙墙,每一個HTTP請求為一個request

除了監(jiān)聽域?qū)ο蟮膭?chuàng)建和銷毀,還可以監(jiān)聽域?qū)ο笾袑傩园l(fā)生修改的事件生均。

  • HttpSessionAttributeListener
  • ServletContextAttributeListener
  • ServletRequestAttributeListener

1.2 Servlet監(jiān)聽器實現(xiàn):

@Slf4j
@WebListener
public class CustomListener implements ServletContextListener,
        ServletRequestListener,
        HttpSessionListener,
        ServletRequestAttributeListener {

    @Override
    public void contextInitialized(ServletContextEvent se) {
        log.info("==============context創(chuàng)建");
    }

    @Override
    public void contextDestroyed(ServletContextEvent se) {
        log.info("==============context銷毀");
    }


    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        log.info(" ++++++++++++++++++request監(jiān)聽器:銷毀");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        log.info(" ++++++++++++++++++request監(jiān)聽器:創(chuàng)建");
    }


    @Override
    public void sessionCreated(HttpSessionEvent se) {
        log.info("----------------session創(chuàng)建");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        log.info("----------------session銷毀");
    }

    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        log.info("----------------attributeAdded");
    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        log.info("----------------attributeRemoved");
    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        log.info("----------------attributeReplaced");
    }
}

在啟動類中加入@ServletComponentScan進行自動注冊即可听想。

1.3 Servlet監(jiān)聽器測試:

@Slf4j
@RequestMapping("/api")
@RestController
public class DemoController {
    
    @GetMapping("/hello")
    public String hello(HttpServletRequest request, HttpSession session){
        request.setAttribute("name", "William");
        request.setAttribute("name", "Jerry");
        request.getAttribute("name");
        request.removeAttribute("age");

        session.setAttribute("name", "William");
        session.getAttribute("name");
        session.invalidate();
        return "hello";
    }
}

二.Servlet過濾器

2.1 定義及使用場景

Servlet 過濾器是可用于 Servlet 編程的 Java 類,有以下目的:

  • 在客戶端的請求訪問后端資源之前马胧,攔截這些請求汉买。
  • 在服務(wù)器的響應(yīng)發(fā)送回客戶端之前,處理這些響應(yīng)佩脊。

在實際的應(yīng)用開發(fā)中蛙粘,我們經(jīng)常使用過濾器做以下的一些事情

  • 基于一定的授權(quán)邏輯,對HTTP請求進行過濾威彰,從而保證數(shù)據(jù)訪問的安全出牧。比如:判斷請求的來源IP是否在系統(tǒng)黑名單中
  • 對于一些經(jīng)過加密的HTTP請求數(shù)據(jù),進行統(tǒng)一解密抱冷,方便后端資源進行業(yè)務(wù)處理
  • 或者我們社交應(yīng)用經(jīng)常需要的敏感詞過濾崔列,也可以使用過濾器梢褐,將觸發(fā)敏感詞的非法請求過濾掉

過濾器主要的特點在于:一是可以過濾所有請求旺遮,二是它能夠改變請求的數(shù)據(jù)內(nèi)容赵讯。

2.2 過濾器的實現(xiàn)

@WebFilter(filterName="customFilter",urlPatterns={"/*"})
@Slf4j
public class CustomFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("filter 初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("customFilter 請求處理之前----doFilter方法之前過濾請求");
        //對request、response進行一些預(yù)處理
        // 比如設(shè)置請求編碼
        // request.setCharacterEncoding("UTF-8");
        // response.setCharacterEncoding("UTF-8");

        //鏈路 直接傳給下一個過濾器
        chain.doFilter(request, response);

        log.info("customFilter 請求處理之后----doFilter方法之后處理響應(yīng)");
    }

    @Override
    public void destroy() {
        log.info("filter 銷毀");
    }
}

然后在啟動類加入@ServletComponentScan注解即可耿眉。

三.Spring攔截器

3.1 攔截器定義

在 Servlet 規(guī)范中并沒有攔截器的概念边翼,它是在Spring框架內(nèi)衍生出來。
Spring中攔截器有三個方法:

  • preHandle 表示被攔截的URL對應(yīng)的控制層方法鸣剪,執(zhí)行前的自定義處理邏輯
  • postHandle 表示被攔截的URL對應(yīng)的控制層方法组底,執(zhí)行后的自定義處理邏輯,此時還未將modelAndView進行頁面渲染筐骇。
  • afterCompletion 表示此時modelAndView已做頁面渲染债鸡,執(zhí)行攔截器的自定義處理。

3.2 攔截器與過濾器的核心區(qū)別

從請求處理的生命周期上看铛纬,攔截器Interceptor和過濾器filter的作用是類似的厌均。過濾球能做的事情,攔截器幾乎也都能做告唆。
但是二者使用場景還是有一些區(qū)別的:

  • 規(guī)范不同:Filter是在Servlet規(guī)范中定義的組件棺弊,在servlet容器內(nèi)生效。而攔截器是Spring框架支持的擒悬,在Spring 上下文中生效模她。
  • 攔截器可以獲取并使用Spring IOC容器中的bean,但過濾器就不行懂牧。因為過濾器是Servlet的組件侈净,而IOC容器的bean是Spring框架內(nèi)使用,攔截器恰恰是Spring框架內(nèi)衍生出來的僧凤。
  • 攔截器可以訪問Spring上下文值對象用狱,如ModelAndView,過濾器不行拼弃∠囊粒基于與上一點同樣的原因。
  • 過濾器在進入servlet容器之前處理請求吻氧,攔截器在servlet容器之內(nèi)處理請求溺忧。過濾器比攔截器的粒度更大,比較適合系統(tǒng)級別的所有API的處理動作盯孙。比如:權(quán)限認(rèn)證鲁森,Spring Security就大量的使用了過濾器。
  • 攔截器相比于過濾器粒度更小振惰,更適合分模塊歌溉、分范圍的統(tǒng)一業(yè)務(wù)邏輯處理。比如:分模塊的、分業(yè)務(wù)的記錄審計日志痛垛。(后面在日志的管理的那一章草慧,我們會為介紹使用攔截器實現(xiàn)統(tǒng)一訪問日志的記錄)

3.3 攔截器的實現(xiàn)

1.定義攔截器:

@Slf4j
@Component
public class CustomHandlerInterceptor implements HandlerInterceptor {

     @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         log.info("preHandle:請求前調(diào)用");
         //返回 false 則請求中斷
         return true;
     }

     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
          log.info("postHandle:請求后調(diào)用");
     }

     @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion:請求調(diào)用完成后回調(diào)方法,即在視圖渲染完成后回調(diào)");
    }
}

2.通過實現(xiàn)WebMvcConfigurer接口完成攔截器的注冊:

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

        @Resource
        private CustomHandlerInterceptor customHandlerInterceptor;

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //注冊攔截器 攔截規(guī)則
            //多個攔截器時 以此添加 執(zhí)行順序按添加順序
            registry.addInterceptor(customHandlerInterceptor);
        }
}

四.Spring事件監(jiān)聽

4.1.事件監(jiān)聽的角色

首先我們要理解事件監(jiān)聽中需要的幾個角色

  • 事件發(fā)布者 (即事件源)
  • 事件監(jiān)聽者
  • 事件本身

4.2. 事件監(jiān)聽的使用場景

為了將技術(shù)問題簡單化匙头,為大家舉一個簡單的例子漫谷。比如居委會發(fā)布停水通知。居委會就是事件源蹂析、停水就是事件本身舔示、該居委會的轄區(qū)居民就是事件監(jiān)聽者。大家看這個例子电抚,有這樣幾個特點:

  • 異步處理:居委會工作人員發(fā)布通知之后惕稻,就可以去忙別的工作了,不會原地等待所有居民的反饋蝙叛。
  • 解耦:居委會和居民之間是解耦的缩宜,互相不干擾對方的工作狀態(tài)與生活狀態(tài)。
  • 不規(guī)律性:對于停水的事件發(fā)生頻率是不規(guī)律的甥温,觸發(fā)規(guī)則相對隨機锻煌。

當(dāng)你在一個系統(tǒng)的業(yè)務(wù)需求中,滿足上面的幾個特點中的2點姻蚓,就應(yīng)該考慮使用事件監(jiān)聽機制實現(xiàn)業(yè)務(wù)需求宋梧。當(dāng)然實現(xiàn)事件監(jiān)聽機制有很的方法,比如:

  • 使用消息隊列中間件的發(fā)布訂閱模式
  • JDK自帶的java.util.EventListener
  • 本節(jié)為大家介紹的是:Spring環(huán)境下的實現(xiàn)事件發(fā)布監(jiān)聽的方法

4.3 代碼實現(xiàn)

1.自定義事件
繼承自ApplicationEvent抽象類狰挡,然后定義自己的構(gòu)造器捂龄。

public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
       super(source);
    }
}

2.自定義事件監(jiān)聽器

@Slf4j
@Component
public class MyListener implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent event) {
        log.info(String.format("%s監(jiān)聽到事件源:%s.", MyListener.class.getName(), event.getSource()));
    }
}

4.4 測試監(jiān)聽事件的發(fā)布

@Slf4j
@RequestMapping("/api")
@RestController
public class DemoController {

    @Resource
    private ApplicationContext applicationContext;

    @GetMapping("/publish")
    public void publishEvent() {
        applicationContext.publishEvent(new MyEvent("測試事件."));
    }
}

五.SpringBoot啟動監(jiān)聽器

5.1 簡介

Spring Boot提供了兩個接口:CommandLineRunner、ApplicationRunner加叁,用于啟動應(yīng)用時做特殊處理倦沧,這些代碼會在SpringApplication的run()方法運行完成之前被執(zhí)行。相對于之前介紹的Spring的ApplicationListener接口自定義監(jiān)聽器它匕、Servlet的ServletContextListener監(jiān)聽器展融。使用二者的好處在于,可以方便的使用應(yīng)用啟動參數(shù)豫柬,根據(jù)參數(shù)不同做不同的初始化操作告希。

5.2 常用場景介紹

實現(xiàn)CommandLineRunner、ApplicationRunner接口烧给。通常用于應(yīng)用啟動前的特殊代碼執(zhí)行燕偶,比如:

  • 將系統(tǒng)常用的數(shù)據(jù)加載到內(nèi)存
  • 應(yīng)用上一次運行的垃圾數(shù)據(jù)清理
  • 系統(tǒng)啟動成功后的通知的發(fā)送

5.3 啟動監(jiān)聽器實現(xiàn)

1.CommandLineRunner:參數(shù)是字符串?dāng)?shù)組

@Slf4j
@Component
public class CommandLineStartupRunner implements CommandLineRunner {
    @Override
    public void run(String... args){
        log.info("CommandLineRunner傳入?yún)?shù):{}", Arrays.toString(args));
    }
}

2.ApplicationRunner:參數(shù)被放入ApplicationArguments,通過getOptionNames()础嫡、getOptionValues()指么、getSourceArgs()獲取參數(shù)

@Slf4j
@Component
public class AppStartupRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args)  {
        log.info("ApplicationRunner參數(shù)名稱: {}", args.getOptionNames());
        log.info("ApplicationRunner參數(shù)值: {}", args.getOptionValues("age"));
        log.info("ApplicationRunner參數(shù): {}", Arrays.toString(args.getSourceArgs()));
    }
}

5.4總結(jié)

CommandLineRunner、ApplicationRunner的核心用法是一致的,就是用于應(yīng)用啟動前的特殊代碼執(zhí)行伯诬。ApplicationRunner的執(zhí)行順序先于CommandLineRunner晚唇;ApplicationRunner將參數(shù)封裝成了對象,提供了獲取參數(shù)名姑廉、參數(shù)值等方法,操作上會方便一些翁涤。
當(dāng)定義多個監(jiān)聽器時桥言,SpringBoot是通過遍歷來啟動所有的Runner,只有上一個Runner執(zhí)行完成之后葵礼,才會執(zhí)行下一個Runner号阿,是同步執(zhí)行的。如果在某個Runner實現(xiàn)是run方法體中調(diào)用了同步阻塞的API或者是一個 while(true) 循環(huán)鸳粉,在遍歷中處于該Runner之后的其他實現(xiàn)將不會被執(zhí)行扔涧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市届谈,隨后出現(xiàn)的幾起案子枯夜,更是在濱河造成了極大的恐慌,老刑警劉巖艰山,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湖雹,死亡現(xiàn)場離奇詭異,居然都是意外死亡曙搬,警方通過查閱死者的電腦和手機摔吏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纵装,“玉大人征讲,你說我怎么就攤上這事∠鹇Γ” “怎么了罗丰?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵衙荐,是天一觀的道長。 經(jīng)常有香客問我,道長耀鸦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任嗤形,我火速辦了婚禮屋吨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棘劣。我一直安慰自己俏让,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著首昔,像睡著了一般寡喝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勒奇,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天预鬓,我揣著相機與錄音,去河邊找鬼赊颠。 笑死格二,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的竣蹦。 我是一名探鬼主播顶猜,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痘括!你這毒婦竟也來了长窄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤纲菌,失蹤者是張志新(化名)和其女友劉穎挠日,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翰舌,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡肆资,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灶芝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郑原。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖夜涕,靈堂內(nèi)的尸體忽然破棺而出犯犁,到底是詐尸還是另有隱情,我是刑警寧澤女器,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布酸役,位于F島的核電站,受9級特大地震影響驾胆,放射性物質(zhì)發(fā)生泄漏涣澡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一丧诺、第九天 我趴在偏房一處隱蔽的房頂上張望入桂。 院中可真熱鬧,春花似錦驳阎、人聲如沸抗愁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜘腌。三九已至沫屡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撮珠,已是汗流浹背沮脖。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芯急,地道東北人勺届。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像志于,于是被迫代替她去往敵國和親涮因。 傳聞我的和親對象是個殘疾皇子废睦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容