1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Springboot 中使用監(jiān)聽(tīng)器

        共 13313字,需瀏覽 27分鐘

         ·

        2021-02-26 10:44

        本來(lái)已收錄到我寫(xiě)的10萬(wàn)字Springboot經(jīng)典學(xué)習(xí)筆記中,筆記在持續(xù)更新……文末有領(lǐng)取方式

        1. 監(jiān)聽(tīng)器介紹

        什么是 web 監(jiān)聽(tīng)器?web 監(jiān)聽(tīng)器是一種 Servlet 中特殊的類(lèi),它們能幫助開(kāi)發(fā)者監(jiān)聽(tīng) web 中特定的事件,比如 ServletContext, HttpSession, ServletRequest 的創(chuàng)建和銷(xiāo)毀;變量的創(chuàng)建、銷(xiāo)毀和修改等??梢栽谀承﹦?dòng)作前后增加處理,實(shí)現(xiàn)監(jiān)控。

        2. Spring Boot中監(jiān)聽(tīng)器的使用

        web 監(jiān)聽(tīng)器的使用場(chǎng)景很多,比如監(jiān)聽(tīng) servlet 上下文用來(lái)初始化一些數(shù)據(jù)、監(jiān)聽(tīng) http session 用來(lái)獲取當(dāng)前在線(xiàn)的人數(shù)、監(jiān)聽(tīng)客戶(hù)端請(qǐng)求的 servlet request 對(duì)象來(lái)獲取用戶(hù)的訪(fǎng)問(wèn)信息等等。這一節(jié)中,我們主要通過(guò)這三個(gè)實(shí)際的使用場(chǎng)景來(lái)學(xué)習(xí)一下 Spring Boot 中監(jiān)聽(tīng)器的使用。

        2.1 監(jiān)聽(tīng)Servlet上下文對(duì)象

        監(jiān)聽(tīng) servlet 上下文對(duì)象可以用來(lái)初始化數(shù)據(jù),用于緩存。什么意思呢?我舉一個(gè)很常見(jiàn)的場(chǎng)景,比如用戶(hù)在點(diǎn)擊某個(gè)站點(diǎn)的首頁(yè)時(shí),一般都會(huì)展現(xiàn)出首頁(yè)的一些信息,而這些信息基本上或者大部分時(shí)間都保持不變的,但是這些信息都是來(lái)自數(shù)據(jù)庫(kù)。如果用戶(hù)的每次點(diǎn)擊,都要從數(shù)據(jù)庫(kù)中去獲取數(shù)據(jù)的話(huà),用戶(hù)量少還可以接受,如果用戶(hù)量非常大的話(huà),這對(duì)數(shù)據(jù)庫(kù)也是一筆很大的開(kāi)銷(xiāo)。

        針對(duì)這種首頁(yè)數(shù)據(jù),大部分都不常更新的話(huà),我們完全可以把它們緩存起來(lái),每次用戶(hù)點(diǎn)擊的時(shí)候,我們都直接從緩存中拿,這樣既可以提高首頁(yè)的訪(fǎng)問(wèn)速度,又可以降低服務(wù)器的壓力。如果做的更加靈活一點(diǎn),可以再加個(gè)定時(shí)器,定期的來(lái)更新這個(gè)首頁(yè)緩存。就類(lèi)似與 CSDN 個(gè)人博客首頁(yè)中排名的變化一樣。

        下面我們針對(duì)這個(gè)功能,來(lái)寫(xiě)一個(gè) demo,在實(shí)際中,讀者可以完全套用該代碼,來(lái)實(shí)現(xiàn)自己項(xiàng)目中的相關(guān)邏輯。首先寫(xiě)一個(gè) Service,模擬一下從數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù):

        @Service
        public class UserService {

            /**
             * 獲取用戶(hù)信息
             * @return
             */

            public User getUser() {
                // 實(shí)際中會(huì)根據(jù)具體的業(yè)務(wù)場(chǎng)景,從數(shù)據(jù)庫(kù)中查詢(xún)對(duì)應(yīng)的信息
                return new User(1L"倪升武""123456");
            }
        }

        然后寫(xiě)一個(gè)監(jiān)聽(tīng)器,實(shí)現(xiàn) ApplicationListener<ContextRefreshedEvent> 接口,重寫(xiě) onApplicationEvent 方法,將 ContextRefreshedEvent 對(duì)象傳進(jìn)去。如果我們想在加載或刷新應(yīng)用上下文時(shí),也重新刷新下我們預(yù)加載的資源,就可以通過(guò)監(jiān)聽(tīng) ContextRefreshedEvent 來(lái)做這樣的事情。如下:

        /**
         * 使用ApplicationListener來(lái)初始化一些數(shù)據(jù)到application域中的監(jiān)聽(tīng)器
         * @author shengni ni
         * @date 2018/07/05
         */

        @Component
        public class MyServletContextListener implements ApplicationListener<ContextRefreshedEvent{

            @Override
            public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
                // 先獲取到application上下文
                ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
                // 獲取對(duì)應(yīng)的service
                UserService userService = applicationContext.getBean(UserService.class);
                User user = userService.getUser();
                // 獲取application域?qū)ο?,將查到的信息放到application域中
                ServletContext application = applicationContext.getBean(ServletContext.class);
                application.setAttribute("user", user);
            }
        }

        正如注釋中描述的一樣,首先通過(guò) contextRefreshedEvent 來(lái)獲取 application 上下文,再通過(guò) application 上下文來(lái)獲取 UserService 這個(gè) bean,項(xiàng)目中可以根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景,也可以獲取其他的 bean,然后再調(diào)用自己的業(yè)務(wù)代碼獲取相應(yīng)的數(shù)據(jù),最后存儲(chǔ)到 application 域中,這樣前端在請(qǐng)求相應(yīng)數(shù)據(jù)的時(shí)候,我們就可以直接從 application 域中獲取信息,減少數(shù)據(jù)庫(kù)的壓力。下面寫(xiě)一個(gè) Controller 直接從 application 域中獲取 user 信息來(lái)測(cè)試一下。

        @RestController
        @RequestMapping("/listener")
        public class TestController {

            @GetMapping("/user")
            public User getUser(HttpServletRequest request) {
                ServletContext application = request.getServletContext();
                return (User) application.getAttribute("user");
            }
        }

        啟動(dòng)項(xiàng)目,在瀏覽器中輸入 http://localhost:8080/listener/user 測(cè)試一下即可,如果正常返回 user 信息,那么說(shuō)明數(shù)據(jù)已經(jīng)緩存成功。不過(guò) application 這種是緩存在內(nèi)存中,對(duì)內(nèi)存會(huì)有消耗,后面的課程中我會(huì)講到 redis,到時(shí)候再給大家介紹一下 redis 的緩存。

        2.2 監(jiān)聽(tīng)HTTP會(huì)話(huà) Session對(duì)象

        監(jiān)聽(tīng)器還有一個(gè)比較常用的地方就是用來(lái)監(jiān)聽(tīng) session 對(duì)象,來(lái)獲取在線(xiàn)用戶(hù)數(shù)量,現(xiàn)在有很多開(kāi)發(fā)者都有自己的網(wǎng)站,監(jiān)聽(tīng) session 來(lái)獲取當(dāng)前在下用戶(hù)數(shù)量是個(gè)很常見(jiàn)的使用場(chǎng)景,下面來(lái)介紹一下如何來(lái)使用。

        /**
         * 使用HttpSessionListener統(tǒng)計(jì)在線(xiàn)用戶(hù)數(shù)的監(jiān)聽(tīng)器
         * @author shengwu ni
         * @date 2018/07/05
         */

        @Component
        public class MyHttpSessionListener implements HttpSessionListener {

            private static final Logger logger = LoggerFactory.getLogger(MyHttpSessionListener.class);

            /**
             * 記錄在線(xiàn)的用戶(hù)數(shù)量
             */

            public Integer count = 0;

            @Override
            public synchronized void sessionCreated(HttpSessionEvent httpSessionEvent) {
                logger.info("新用戶(hù)上線(xiàn)了");
                count++;
                httpSessionEvent.getSession().getServletContext().setAttribute("count", count);
            }

            @Override
            public synchronized void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
                logger.info("用戶(hù)下線(xiàn)了");
                count--;
                httpSessionEvent.getSession().getServletContext().setAttribute("count", count);
            }
        }

        可以看出,首先該監(jiān)聽(tīng)器需要實(shí)現(xiàn) HttpSessionListener 接口,然后重寫(xiě) sessionCreatedsessionDestroyed 方法,在 sessionCreated 方法中傳遞一個(gè) HttpSessionEvent 對(duì)象,然后將當(dāng)前 session 中的用戶(hù)數(shù)量加1,sessionDestroyed 方法剛好相反,不再贅述。然后我們寫(xiě)一個(gè) Controller 來(lái)測(cè)試一下。

        @RestController
        @RequestMapping("/listener")
        public class TestController {

            /**
             * 獲取當(dāng)前在線(xiàn)人數(shù),該方法有bug
             * @param request
             * @return
             */

            @GetMapping("/total")
            public String getTotalUser(HttpServletRequest request) {
                Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
                return "當(dāng)前在線(xiàn)人數(shù):" + count;
            }
        }

        該 Controller 中是直接獲取當(dāng)前 session 中的用戶(hù)數(shù)量,啟動(dòng)服務(wù)器,在瀏覽器中輸入 localhost:8080/listener/total 可以看到返回的結(jié)果是1,再打開(kāi)一個(gè)瀏覽器,請(qǐng)求相同的地址可以看到 count 是 2 ,這沒(méi)有問(wèn)題。但是如果關(guān)閉一個(gè)瀏覽器再打開(kāi),理論上應(yīng)該還是2,但是實(shí)際測(cè)試卻是 3。原因是 session 銷(xiāo)毀的方法沒(méi)有執(zhí)行(可以在后臺(tái)控制臺(tái)觀察日志打印情況),當(dāng)重新打開(kāi)時(shí),服務(wù)器找不到用戶(hù)原來(lái)的 session,于是又重新創(chuàng)建了一個(gè) session,那怎么解決該問(wèn)題呢?我們可以將上面的 Controller 方法改造一下:

        @GetMapping("/total2")
        public String getTotalUser(HttpServletRequest request, HttpServletResponse response) {
            Cookie cookie;
            try {
                // 把sessionId記錄在瀏覽器中
                cookie = new Cookie("JSESSIONID", URLEncoder.encode(request.getSession().getId(), "utf-8"));
                cookie.setPath("/");
                //設(shè)置cookie有效期為2天,設(shè)置長(zhǎng)一點(diǎn)
                cookie.setMaxAge( 48*60 * 60);
                response.addCookie(cookie);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
            return "當(dāng)前在線(xiàn)人數(shù):" + count;
        }

        可以看出,該處理邏輯是讓服務(wù)器記得原來(lái)那個(gè) session,即把原來(lái)的 sessionId 記錄在瀏覽器中,下次再打開(kāi)時(shí),把這個(gè) sessionId 傳過(guò)去,這樣服務(wù)器就不會(huì)重新再創(chuàng)建了。重啟一下服務(wù)器,在瀏覽器中再次測(cè)試一下,即可避免上面的問(wèn)題。

        2.3 監(jiān)聽(tīng)客戶(hù)端請(qǐng)求Servlet Request對(duì)象

        使用監(jiān)聽(tīng)器獲取用戶(hù)的訪(fǎng)問(wèn)信息比較簡(jiǎn)單,實(shí)現(xiàn) ServletRequestListener 接口即可,然后通過(guò) request 對(duì)象獲取一些信息。如下:

        /**
         * 使用ServletRequestListener獲取訪(fǎng)問(wèn)信息
         * @author shengwu ni
         * @date 2018/07/05
         */

        @Component
        public class MyServletRequestListener implements ServletRequestListener {

            private static final Logger logger = LoggerFactory.getLogger(MyServletRequestListener.class);

            @Override
            public void requestInitialized(ServletRequestEvent servletRequestEvent) {
                HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
                logger.info("session id為:{}", request.getRequestedSessionId());
                logger.info("request url為:{}", request.getRequestURL());

                request.setAttribute("name""倪升武");
            }

            @Override
            public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

                logger.info("request end");
                HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest();
                logger.info("request域中保存的name值為:{}", request.getAttribute("name"));

            }

        }

        這個(gè)比較簡(jiǎn)單,不再贅述,接下來(lái)寫(xiě)一個(gè) Controller 測(cè)試一下即可。

        @GetMapping("/request")
        public String getRequestInfo(HttpServletRequest request) {
            System.out.println("requestListener中的初始化的name數(shù)據(jù):" + request.getAttribute("name"));
            return "success";
        }

        3. Spring Boot中自定義事件監(jiān)聽(tīng)

        在實(shí)際項(xiàng)目中,我們往往需要自定義一些事件和監(jiān)聽(tīng)器來(lái)滿(mǎn)足業(yè)務(wù)場(chǎng)景,比如在微服務(wù)中會(huì)有這樣的場(chǎng)景:微服務(wù) A 在處理完某個(gè)邏輯之后,需要通知微服務(wù) B 去處理另一個(gè)邏輯,或者微服務(wù) A 處理完某個(gè)邏輯之后,需要將數(shù)據(jù)同步到微服務(wù) B,這種場(chǎng)景非常普遍,這個(gè)時(shí)候,我們可以自定義事件以及監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng),一旦監(jiān)聽(tīng)到微服務(wù) A 中的某事件發(fā)生,就去通知微服務(wù) B 處理對(duì)應(yīng)的邏輯。

        3.1 自定義事件

        自定義事件需要繼承 ApplicationEvent 對(duì)象,在事件中定義一個(gè) User 對(duì)象來(lái)模擬數(shù)據(jù),構(gòu)造方法中將 User 對(duì)象傳進(jìn)來(lái)初始化。如下:

        /**
         * 自定義事件
         * @author shengwu ni
         * @date 2018/07/05
         */

        public class MyEvent extends ApplicationEvent {

            private User user;

            public MyEvent(Object source, User user) {
                super(source);
                this.user = user;
            }

            // 省去get、set方法
        }

        3.2 自定義監(jiān)聽(tīng)器

        接下來(lái),自定義一個(gè)監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng)上面定義的 MyEvent 事件,自定義監(jiān)聽(tīng)器需要實(shí)現(xiàn) ApplicationListener 接口即可。如下:

        /**
         * 自定義監(jiān)聽(tīng)器,監(jiān)聽(tīng)MyEvent事件
         * @author shengwu ni
         * @date 2018/07/05
         */

        @Component
        public class MyEventListener implements ApplicationListener<MyEvent{
            @Override
            public void onApplicationEvent(MyEvent myEvent) {
                // 把事件中的信息獲取到
                User user = myEvent.getUser();
                // 處理事件,實(shí)際項(xiàng)目中可以通知?jiǎng)e的微服務(wù)或者處理其他邏輯等等
                System.out.println("用戶(hù)名:" + user.getUsername());
                System.out.println("密碼:" + user.getPassword());

            }
        }

        然后重寫(xiě) onApplicationEvent 方法,將自定義的 MyEvent 事件傳進(jìn)來(lái),因?yàn)樵撌录?,我們定義了 User 對(duì)象(該對(duì)象在實(shí)際中就是需要處理的數(shù)據(jù),在下文來(lái)模擬),然后就可以使用該對(duì)象的信息了。

        OK,定義好了事件和監(jiān)聽(tīng)器之后,需要手動(dòng)發(fā)布事件,這樣監(jiān)聽(tīng)器才能監(jiān)聽(tīng)到,這需要根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景來(lái)觸發(fā),針對(duì)本文的例子,我寫(xiě)個(gè)觸發(fā)邏輯,如下:

        /**
         * UserService
         * @author shengwu ni
         */

        @Service
        public class UserService {

            @Resource
            private ApplicationContext applicationContext;

            /**
             * 發(fā)布事件
             * @return
             */

            public User getUser2() {
                User user = new User(1L"倪升武""123456");
                // 發(fā)布事件
                MyEvent event = new MyEvent(this, user);
                applicationContext.publishEvent(event);
                return user;
            }
        }

        在 service 中注入 ApplicationContext,在業(yè)務(wù)代碼處理完之后,通過(guò) ApplicationContext 對(duì)象手動(dòng)發(fā)布 MyEvent 事件,這樣我們自定義的監(jiān)聽(tīng)器就能監(jiān)聽(tīng)到,然后處理監(jiān)聽(tīng)器中寫(xiě)好的業(yè)務(wù)邏輯。

        最后,在 Controller 中寫(xiě)一個(gè)接口來(lái)測(cè)試一下:

        @GetMapping("/request")
        public String getRequestInfo(HttpServletRequest request) {
            System.out.println("requestListener中的初始化的name數(shù)據(jù):" + request.getAttribute("name"));
            return "success";
        }

        在瀏覽器中輸入 http://localhost:8080/listener/publish,然后觀察一下控制臺(tái)打印的用戶(hù)名和密碼,即可說(shuō)明自定義監(jiān)聽(tīng)器已經(jīng)生效。

        4 總結(jié)

        本課系統(tǒng)的介紹了監(jiān)聽(tīng)器原理,以及在 Spring Boot 中如何使用監(jiān)聽(tīng)器,列舉了監(jiān)聽(tīng)器的三個(gè)常用的案例,有很好的實(shí)戰(zhàn)意義。最后講解了項(xiàng)目中如何自定義事件和監(jiān)聽(tīng)器,并結(jié)合微服務(wù)中常見(jiàn)的場(chǎng)景,給出具體的代碼模型,均能運(yùn)用到實(shí)際項(xiàng)目中去,希望讀者認(rèn)真消化。

        該文已收錄到我寫(xiě)的《10萬(wàn)字Springboot經(jīng)典學(xué)習(xí)筆記》中,點(diǎn)擊下面小卡片,進(jìn)入【Java開(kāi)發(fā)寶典】,回復(fù):筆記,即可免費(fèi)獲取。


        點(diǎn)贊是最大的支持 

        瀏覽 82
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            肉体秘书hd日本在线观看 | www.啊啊啊啊 | 国产精品熟女高潮无套 | 亲子乱婬A片日韩精品一区 | 国产精品久久久久久妇女6080 | 91毛片网 | 波多野结衣在线观看一区二区 | 女生呻吟声 | 人人干av | 国产精品美女久久久久 |