<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 13313字,需瀏覽 27分鐘

           ·

          2021-02-26 10:44

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

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

          什么是 web 監(jiān)聽器?web 監(jiān)聽器是一種 Servlet 中特殊的類,它們能幫助開發(fā)者監(jiān)聽 web 中特定的事件,比如 ServletContext, HttpSession, ServletRequest 的創(chuàng)建和銷毀;變量的創(chuàng)建、銷毀和修改等。可以在某些動作前后增加處理,實現(xiàn)監(jiān)控。

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

          web 監(jiān)聽器的使用場景很多,比如監(jiān)聽 servlet 上下文用來初始化一些數(shù)據(jù)、監(jiān)聽 http session 用來獲取當前在線的人數(shù)、監(jiān)聽客戶端請求的 servlet request 對象來獲取用戶的訪問信息等等。這一節(jié)中,我們主要通過這三個實際的使用場景來學習一下 Spring Boot 中監(jiān)聽器的使用。

          2.1 監(jiān)聽Servlet上下文對象

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

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

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

          @Service
          public class UserService {

              /**
               * 獲取用戶信息
               * @return
               */

              public User getUser() {
                  // 實際中會根據(jù)具體的業(yè)務場景,從數(shù)據(jù)庫中查詢對應的信息
                  return new User(1L"倪升武""123456");
              }
          }

          然后寫一個監(jiān)聽器,實現(xiàn) ApplicationListener<ContextRefreshedEvent> 接口,重寫 onApplicationEvent 方法,將 ContextRefreshedEvent 對象傳進去。如果我們想在加載或刷新應用上下文時,也重新刷新下我們預加載的資源,就可以通過監(jiān)聽 ContextRefreshedEvent 來做這樣的事情。如下:

          /**
           * 使用ApplicationListener來初始化一些數(shù)據(jù)到application域中的監(jiān)聽器
           * @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();
                  // 獲取對應的service
                  UserService userService = applicationContext.getBean(UserService.class);
                  User user = userService.getUser();
                  // 獲取application域?qū)ο螅瑢⒉榈降男畔⒎诺絘pplication域中
                  ServletContext application = applicationContext.getBean(ServletContext.class);
                  application.setAttribute("user", user);
              }
          }

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

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

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

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

          2.2 監(jiān)聽HTTP會話 Session對象

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

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

          @Component
          public class MyHttpSessionListener implements HttpSessionListener {

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

              /**
               * 記錄在線的用戶數(shù)量
               */

              public Integer count = 0;

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

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

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

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

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

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

          該 Controller 中是直接獲取當前 session 中的用戶數(shù)量,啟動服務器,在瀏覽器中輸入 localhost:8080/listener/total 可以看到返回的結(jié)果是1,再打開一個瀏覽器,請求相同的地址可以看到 count 是 2 ,這沒有問題。但是如果關(guān)閉一個瀏覽器再打開,理論上應該還是2,但是實際測試卻是 3。原因是 session 銷毀的方法沒有執(zhí)行(可以在后臺控制臺觀察日志打印情況),當重新打開時,服務器找不到用戶原來的 session,于是又重新創(chuàng)建了一個 session,那怎么解決該問題呢?我們可以將上面的 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("/");
                  //設置cookie有效期為2天,設置長一點
                  cookie.setMaxAge( 48*60 * 60);
                  response.addCookie(cookie);
              } catch (UnsupportedEncodingException e) {
                  e.printStackTrace();
              }
              Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
              return "當前在線人數(shù):" + count;
          }

          可以看出,該處理邏輯是讓服務器記得原來那個 session,即把原來的 sessionId 記錄在瀏覽器中,下次再打開時,把這個 sessionId 傳過去,這樣服務器就不會重新再創(chuàng)建了。重啟一下服務器,在瀏覽器中再次測試一下,即可避免上面的問題。

          2.3 監(jiān)聽客戶端請求Servlet Request對象

          使用監(jiān)聽器獲取用戶的訪問信息比較簡單,實現(xiàn) ServletRequestListener 接口即可,然后通過 request 對象獲取一些信息。如下:

          /**
           * 使用ServletRequestListener獲取訪問信息
           * @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"));

              }

          }

          這個比較簡單,不再贅述,接下來寫一個 Controller 測試一下即可。

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

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

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

          3.1 自定義事件

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

          /**
           * 自定義事件
           * @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)聽器

          接下來,自定義一個監(jiān)聽器來監(jiān)聽上面定義的 MyEvent 事件,自定義監(jiān)聽器需要實現(xiàn) ApplicationListener 接口即可。如下:

          /**
           * 自定義監(jiān)聽器,監(jiān)聽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();
                  // 處理事件,實際項目中可以通知別的微服務或者處理其他邏輯等等
                  System.out.println("用戶名:" + user.getUsername());
                  System.out.println("密碼:" + user.getPassword());

              }
          }

          然后重寫 onApplicationEvent 方法,將自定義的 MyEvent 事件傳進來,因為該事件中,我們定義了 User 對象(該對象在實際中就是需要處理的數(shù)據(jù),在下文來模擬),然后就可以使用該對象的信息了。

          OK,定義好了事件和監(jiān)聽器之后,需要手動發(fā)布事件,這樣監(jiān)聽器才能監(jiān)聽到,這需要根據(jù)實際業(yè)務場景來觸發(fā),針對本文的例子,我寫個觸發(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è)務代碼處理完之后,通過 ApplicationContext 對象手動發(fā)布 MyEvent 事件,這樣我們自定義的監(jiān)聽器就能監(jiān)聽到,然后處理監(jiān)聽器中寫好的業(yè)務邏輯。

          最后,在 Controller 中寫一個接口來測試一下:

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

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

          4 總結(jié)

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

          該文已收錄到我寫的《10萬字Springboot經(jīng)典學習筆記》中,點擊下面小卡片,進入【Java開發(fā)寶典】,回復:筆記,即可免費獲取。


          點贊是最大的支持 

          瀏覽 82
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费日批的视频 | 欧美性猛交ⅩXXX乱大交 | 青青欧美,青青美女视频 | 美女成人视频 | 国产欧美日韩A V片 |