Java Listener監(jiān)聽器使用規(guī)范詳細(xì)介紹
1、什么是監(jiān)聽器以及監(jiān)聽器作用
①監(jiān)聽器是Servlet規(guī)范中的一員。就像Filter一樣。Filter也是Servlet規(guī)范中的一員。
②在Servlet中,所有的監(jiān)聽器接口都是以“Listener”結(jié)尾。
③監(jiān)聽器實(shí)際上是Servlet規(guī)范留給我們javaweb程序員的特殊時(shí)機(jī)。
④特殊的時(shí)刻如果想執(zhí)行這段代碼,你需要想到使用對應(yīng)的監(jiān)聽器。
2、Servlet規(guī)范中提供了哪些監(jiān)聽器
javax.servlet包下:
①ServletContextListener
②ServletContextAttributeListener
③ServletRequestListener
④ServletRequestAttributeListener
jakarta.servlet.http包下:
①HttpSessionListener
②HttpSessionAttributeListener
③HttpSessionBindingListener
④HttpSessionIdListener
⑤HttpSessionActivationListener
3、實(shí)現(xiàn)一個(gè)監(jiān)聽器的步驟
這里主要先講解熟悉的關(guān)于三個(gè)域?qū)ο蟮谋O(jiān)聽器:
ServletContext、ServletRequest、HttpSession
(1)以ServletContextListener為例
①第一步:編寫一個(gè)類實(shí)現(xiàn)ServletContextListener接口。并且實(shí)現(xiàn)里面的方法。
監(jiān)聽器中的方法不需要程序員手動調(diào)用。是發(fā)生某個(gè)特殊事件之后被服務(wù)器調(diào)用。
// ServletContext對象被創(chuàng)建的時(shí)候調(diào)用。 void contextInitialized(ServletContextEvent event) // ServletContext對象被銷毀的時(shí)候調(diào)用 void contextDestroyed(ServletContextEvent event)
②第二步:在web.xml文件中對ServletContextListener進(jìn)行配置,如下:
當(dāng)然,第二步也可以不使用配置文件,也可以用注解,例如:@WebListener 即可。
<listener> <listener-class>com.bjpowernode.javaweb.servlet.MyServletContextListener</listener-class> </listener>
注意:所有監(jiān)聽器中的方法都是不需要javaweb程序員調(diào)用的,由服務(wù)器來負(fù)責(zé)調(diào)用。
什么時(shí)候被調(diào)用呢?當(dāng)某個(gè)特殊的事件發(fā)生(特殊的事件發(fā)生其實(shí)就是某個(gè)時(shí)機(jī)到了)之后,被web服務(wù)器自動調(diào)用。
③服務(wù)器啟動時(shí),ServletContext對象創(chuàng)建,contextInitialized方法執(zhí)行
服務(wù)器關(guān)閉時(shí),ServletContext對象銷毀,contextDestroyed方法執(zhí)行
package com.bjpowernode.javaweb.servlet; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** * @Package:com.bjpowernode.javaweb.servlet * @Project:JavaWeb * @name:MyServletContextListener */ // ServletContextListener監(jiān)聽器主要監(jiān)聽的是:ServletContext對象的狀態(tài)。 public class MyServletContextListener implements ServletContextListener { // 服務(wù)器啟動時(shí)間點(diǎn) /** * 監(jiān)聽器中的方法不需要程序員手動調(diào)用。是發(fā)生某個(gè)特殊事件之后被服務(wù)器調(diào)用。 * @param sce */ @Override public void contextInitialized(ServletContextEvent sce) { // 服務(wù)器關(guān)閉時(shí)間點(diǎn) // 現(xiàn)在這個(gè)特殊的時(shí)刻寫代碼,你寫就是了。它會被服務(wù)器自動調(diào)用。 // 這個(gè)方法是在ServletContext對象被創(chuàng)建的時(shí)候調(diào)用。 System.out.println("ServletContext對象創(chuàng)建了。"); } @Override public void contextDestroyed(ServletContextEvent sce) { // 現(xiàn)在這個(gè)特殊的時(shí)刻寫代碼,你寫就是了。它會被服務(wù)器自動調(diào)用。 // 這個(gè)方法是在ServletContext對象被銷毀的時(shí)候調(diào)用。 System.out.println("ServletContext對象被銷毀了。"); } }
(2)以ServletRequestListener為例
④ServletRequest對象是一次請求創(chuàng)建一個(gè)request對象,所以服務(wù)器啟動后:
只要發(fā)送一次請求就會調(diào)用requestInitialized方法,請求結(jié)束立刻會調(diào)用requestDestroyed
注:我們直接訪問http://localhost:8080/servlet15/會報(bào)404錯(cuò)誤,因?yàn)槟J(rèn)會訪問index.html,但是我們并沒有寫;就算如此也會發(fā)送出請求,執(zhí)行這個(gè)監(jiān)聽器。
package com.bjpowernode.javaweb.servlet; import javax.servlet.ServletContextEvent; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; /** * @Package:com.bjpowernode.javaweb.servlet * @Project:JavaWeb * @name:MyServletRequestListener */ @WebListener public class MyServletRequestListener implements ServletRequestListener { // request對象銷毀時(shí)間點(diǎn) @Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("request對象銷毀了"); } // request對象創(chuàng)建時(shí)間點(diǎn) @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("request對象初始化了"); } }
(3)以HttpSessionListener為例
⑤我們都知道在訪問jsp時(shí),默認(rèn)會創(chuàng)建session對象(九大內(nèi)置對象);先編寫一個(gè)my.jsp;在訪問my.jsp時(shí),會創(chuàng)建session對象,調(diào)用 sessionCreated方法;
當(dāng)退出系統(tǒng)時(shí),我們編寫銷毀session對象的方法,會調(diào)用sessionDestroyed方法。
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <title>Title</title> </head> <body> my jsp page <a href="${pageContext.request.contextPath}/exit" rel="external nofollow" >退出系統(tǒng)</a> </body> </html>
根據(jù)/exit請求,編寫銷毀session對象的類
package com.bjpowernode.javaweb; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; /** * @Package:com.bjpowernode.javaweb * @Project:JavaWeb * @name:ExitServlet */ @WebServlet("/exit") public class ExitServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲取session對象 HttpSession session = request.getSession(false); if (session != null) { // 銷毀session session.invalidate(); } } }
(4)AttributeListener的使用
①我們知道對于域?qū)ο蠖加衧etAttribute、getAttribute、removeAttribute方法,分別可以向域中存數(shù)據(jù)、取數(shù)據(jù)、清除數(shù)據(jù);所以對于AttributeListener肯定是和這些處理域中數(shù)據(jù)有關(guān)。
②實(shí)際上對于ServletContextAttributeListener、ServletRequestAttributeListener 、HttpSessionAttributeListener這三個(gè)對象都有attributeAdded、attributeRemoved、attributeReplaced方法;表示:向域當(dāng)中存儲數(shù)據(jù)的時(shí)候調(diào)用、向域當(dāng)中刪除數(shù)據(jù)的時(shí)候調(diào)用、向域當(dāng)中替換數(shù)據(jù)的時(shí)候調(diào)用。
③這里以HttpSessionAttributeListener對象為例:
編寫HttpSessionAttributeListener監(jiān)聽器
package com.bjpowernode.javaweb.servlet; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; /** * @Package:com.bjpowernode.javaweb.servlet * @Project:JavaWeb * @name:MyHttpSessionAttributeListener */ @WebListener public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener { // 向session域當(dāng)中存儲數(shù)據(jù)的時(shí)候,以下方法被WEB服務(wù)器調(diào)用。 @Override public void attributeAdded(HttpSessionBindingEvent se) { System.out.println("session data add"); } // 將session域當(dāng)中存儲的數(shù)據(jù)刪除的時(shí)候,以下方法被WEB服務(wù)器調(diào)用。 @Override public void attributeRemoved(HttpSessionBindingEvent se) { System.out.println("session data remove"); } // session域當(dāng)中的某個(gè)數(shù)據(jù)被替換的時(shí)候,以下方法被WEB服務(wù)器調(diào)用。 @Override public void attributeReplaced(HttpSessionBindingEvent se) { System.out.println("session data replace"); } }
編寫Servlet類用來處理域中的數(shù)據(jù)
當(dāng)發(fā)送http://localhost:8080/servlet15/session/attribute/test就能觸發(fā)上面的監(jiān)聽器;
注意:調(diào)用getAttribute方法不會觸發(fā),只有setAttribute方法和removeAttribute方法才會觸發(fā)
package com.bjpowernode.javaweb; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; /** * @Package:com.bjpowernode.javaweb * @Project:JavaWeb * @name:HttpSessionAttributeServlet */ @WebServlet("/session/attribute/test") public class HttpSessionAttributeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲取session對象 HttpSession session = request.getSession(); // 向session域中存儲數(shù)據(jù) session.setAttribute("user", "zhangsan"); // 替換,覆蓋上面的數(shù)據(jù) session.setAttribute("user", "lisi"); // 刪除 session.removeAttribute("user"); } }
4、HttpSessionBindingListener
(1)前面我們已經(jīng)講解了關(guān)于域?qū)ο蟮谋O(jiān)聽器,九個(gè)監(jiān)聽器中就已經(jīng)學(xué)習(xí)了6個(gè);接下來就先分析一下HttpSessionBindingListener;顧名思義就是關(guān)于數(shù)據(jù)綁定的!
(2)下面就通過一個(gè)例子來學(xué)習(xí)一下HttpSessionBindingListener監(jiān)聽器:
創(chuàng)建一個(gè)user1類實(shí)現(xiàn)監(jiān)聽器(不需要@WebListener注解),并重寫方法
創(chuàng)建一個(gè)user2類不實(shí)現(xiàn)監(jiān)聽器
對比當(dāng)數(shù)據(jù)放入放入域當(dāng)中,兩者會有什么區(qū)別:
①普通的user1類實(shí)現(xiàn)監(jiān)聽器,并重寫監(jiān)聽器中兩個(gè)方法
package com.bjpowernode.javaweb.bean; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; /** * 普通的java類。但是它實(shí)現(xiàn)了:HttpSessionBindingListener */ public class User1 implements HttpSessionBindingListener { @Override public void valueBound(HttpSessionBindingEvent event) { System.out.println("綁定數(shù)據(jù)"); } @Override public void valueUnbound(HttpSessionBindingEvent event) { System.out.println("解綁數(shù)據(jù)"); } private String usercode; private String username; private String password; public User1(String usercode, String username, String password) { this.usercode = usercode; this.username = username; this.password = password; } public User1() { } public String getUsercode() { return usercode; } public void setUsercode(String usercode) { this.usercode = usercode; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
②普通的user2類不實(shí)現(xiàn)監(jiān)聽器
package com.bjpowernode.javaweb.bean; /** * 普通的java類。 */ public class User2 { private String usercode; private String username; private String password; public User2() { } public User2(String usercode, String username, String password) { this.usercode = usercode; this.username = username; this.password = password; } public String getUsercode() { return usercode; } public void setUsercode(String usercode) { this.usercode = usercode; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
③編寫一個(gè)servlet類,把這兩種數(shù)據(jù)都存進(jìn)去
發(fā)現(xiàn)實(shí)現(xiàn)監(jiān)聽器的user1類會觸發(fā)綁定事件!
package com.bjpowernode.javaweb.servlet; import com.bjpowernode.javaweb.bean.User1; import com.bjpowernode.javaweb.bean.User2; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/session/bind") public class HttpSessionBindingListenerServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲取session對象 HttpSession session = request.getSession(); // 準(zhǔn)備兩個(gè)對象:User1 User2 User1 user1 = new User1("111", "zhangsan", "123"); User2 user2 = new User2("111", "zhangsan", "123"); // 將user1存儲到session域 session.setAttribute("user1", user1); // 將user2存儲到session域 session.setAttribute("user2", user2); } }
(3)區(qū)分:HttpSessionAttributeListener 和HttpSessionBindingListener
??HttpSessionAttributeListener監(jiān)聽器
①該監(jiān)聽器是需要使用@WebListener注解進(jìn)行標(biāo)注的(或者使用web.xml文件進(jìn)行配置)
②該監(jiān)聽器監(jiān)聽的是session域中數(shù)據(jù)的變化。只要數(shù)據(jù)變化,則執(zhí)行相應(yīng)的方法;主要監(jiān)測點(diǎn)在session域?qū)ο笊稀?/p>
③監(jiān)聽的是session域,只要把數(shù)據(jù)放入session域就進(jìn)行監(jiān)聽!
??HttpSessionBindingListener監(jiān)聽器
①該監(jiān)聽器是不需要使用@WebListener進(jìn)行標(biāo)注,類直接實(shí)現(xiàn)即可。
②假設(shè)User類實(shí)現(xiàn)了該監(jiān)聽器,那么User對象在被放入session的時(shí)候觸發(fā)bind事件,User對象從session中刪除的時(shí)候,觸發(fā)unbind事件。
③假設(shè)Customer類沒有實(shí)現(xiàn)該監(jiān)聽器,那么Customer對象放入session或者從session刪除的時(shí)候,不會觸發(fā)bind和unbind事件。
④監(jiān)聽的是普通的java對象,那個(gè)類實(shí)現(xiàn)了這個(gè)監(jiān)聽器,就監(jiān)聽那個(gè)類!
??總結(jié):
①對于HttpSessionAttributeListener監(jiān)聽的是任何種類的對象,只要放入session域當(dāng)中就可以;上述user1和user2都可以觸發(fā)!
②對于HttpSessionBindingListener監(jiān)聽的是特殊的對象,只有實(shí)現(xiàn)HttpSessionBindingListener接口的才可以;上述只有user1才能觸發(fā)!
(4)那么這兩個(gè)監(jiān)聽器有什么用呢?
我們通過一個(gè)簡單的業(yè)務(wù)需求了解一下:
業(yè)務(wù)1:編寫一個(gè)功能,記錄該網(wǎng)站實(shí)時(shí)的在線用戶的個(gè)數(shù)
我們可以通過服務(wù)器端有沒有分配session對象,因?yàn)橐粋€(gè)session代表了一個(gè)用戶。有一個(gè)session就代表有一個(gè)用戶。如果你采用這種邏輯去實(shí)現(xiàn)的話,session有多少個(gè),在線用戶就有多少個(gè)。這種方式的話:HttpSessionListener夠用了。session對象只要新建,則count++,然后將count存儲到ServletContext域當(dāng)中,在頁面展示在線人數(shù)即可!
業(yè)務(wù)2:只統(tǒng)計(jì)登錄的用戶的在線數(shù)量
用戶登錄的標(biāo)志是什么?session中曾經(jīng)存儲過User類型的對象。那么這個(gè)時(shí)候可以讓User類型的對象實(shí)現(xiàn)HttpSessionBindingListener監(jiān)聽器,只要User類型對象存儲到session域中,則count++,然后將count++存儲到ServletContext對象中。頁面展示在線人數(shù)即可。
5、HttpSessionIdListener&HttpSessionActivationListener
這兩個(gè)監(jiān)聽器不常用,這里只簡單了解即可:
(1)HttpSessionIdListener:session的id發(fā)生改變的時(shí)候,監(jiān)聽器中的唯一一個(gè)方法就會被調(diào)用。
(2)HttpSessionActivationListener:監(jiān)聽session對象的鈍化和活化的。
①鈍化:session對象從內(nèi)存存儲到硬盤文件。
②活化:從硬盤文件把session恢復(fù)到內(nèi)存。
6、使用監(jiān)聽器統(tǒng)計(jì)網(wǎng)站在線人數(shù)
實(shí)現(xiàn)oa項(xiàng)目中當(dāng)前登錄在線的人數(shù)!
(1)什么代表著用戶登錄了?
??session.setAttribute("user", userObj); User類型的對象只要往session中存儲過,表示有新用戶登錄。
(2)什么代表著用戶退出了?
??session.removeAttribute("user"); User類型的對象從session域中移除了。
??或者有可能是session銷毀了。(session超時(shí)) 。
(3)思考:我們要先思考一下尋訪到什么域里面?
統(tǒng)計(jì)這個(gè)項(xiàng)目當(dāng)匯總的登錄在線人數(shù),一個(gè)人一個(gè)的(session)以下的域肯定都不行,所以只能使用application域(ServletContext)。
①編寫User類實(shí)現(xiàn)監(jiān)聽器的接口;然后修改關(guān)于所有session.getAttribute的代碼
package com.bjpowernode.oa.bean; import javax.servlet.ServletContext; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; /** * @Package:com.bjpowernode.oa.bean * @Project:JavaWeb * @name:User */ public class User implements HttpSessionBindingListener { // 重寫兩個(gè)方法 @Override public void valueBound(HttpSessionBindingEvent event) { // 用戶登錄了 // 相當(dāng)于User類型的對象向session域當(dāng)中存放 // 獲取application域?qū)ο? // event.getSession()獲取到session;在調(diào)用getServletContext()獲取到域?qū)ο? ServletContext application = event.getSession().getServletContext(); // 獲取到在線人數(shù) Object onlioncount = application.getAttribute("onlioncount"); // 第一個(gè)用戶登錄,里面什么都沒有,返回的是一個(gè)null if (onlioncount == null) { application.setAttribute("onlioncount",1); }else { // 直接 onlioncount++有問題,前面是Object類型,強(qiáng)轉(zhuǎn) Integer count = (Integer)onlioncount; count++; // 在存入域當(dāng)中 application.setAttribute("onlioncount",count); } } @Override public void valueUnbound(HttpSessionBindingEvent event) { // 用戶退出了 // 相當(dāng)于User類型的對象向session域當(dāng)中刪除 // 獲取application域?qū)ο? ServletContext application = event.getSession().getServletContext(); // 獲取域當(dāng)中的數(shù)據(jù),肯定不是空 Integer onlioncount = (Integer) application.getAttribute("onlioncount"); onlioncount--; // 在存入域當(dāng)中 application.setAttribute("onlioncount",onlioncount); } // 定義屬性 private String username; private String password; // 構(gòu)造方法 public User() { } public User(String username, String password) { this.username = username; this.password = password; } // setter and getter public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
②修改UserServlet類和WelcomeServlet
// 把用戶名放進(jìn)session session.setAttribute("username",username); // 改為這樣存 User user = new User(username, password); session.setAttribute("user",user);
③修改過濾器LoginFilter
if("/index.jsp".equals(servletPath) || "/welcome".equals(servletPath) || "/dept/login".equals(servletPath) || "/dept/exit".equals(servletPath) || (session != null && session.getAttribute("username") != null)){ // username改為user,因?yàn)榍懊娲鎯Φ拿肿兞? if("/index.jsp".equals(servletPath) || "/welcome".equals(servletPath) || "/dept/login".equals(servletPath) || "/dept/exit".equals(servletPath) || (session != null && session.getAttribute("user") != null)){
④修改list.jsp頁面
<h3>歡迎${username}登錄</h3> <!--修改為--> <h3>歡迎${user.username}登錄,在線人數(shù)${onlioncount}人</h3>
⑤最終達(dá)到的效果
假如開了兩個(gè)瀏覽器,登錄了兩次
點(diǎn)擊退出登錄,刷新另一個(gè)瀏覽器的頁面
到此這篇關(guān)于Java Listener監(jiān)聽器使用規(guī)范詳細(xì)介紹的文章就介紹到這了,更多相關(guān)Java Listener監(jiān)聽器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java?LockSupport實(shí)現(xiàn)原理示例解析
這篇文章主要為大家介紹了java?LockSupport實(shí)現(xiàn)原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01SpringBoot 如何根據(jù)不同profile選擇不同配置
這篇文章主要介紹了SpringBoot 如何根據(jù)不同profile選擇不同配置的操作方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Spring?Boot?+?EasyExcel實(shí)現(xiàn)數(shù)據(jù)導(dǎo)入導(dǎo)出
這篇文章主要介紹了Spring?Boot+EasyExcel實(shí)現(xiàn)數(shù)據(jù)導(dǎo)入導(dǎo)出,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08教你在?Java?中實(shí)現(xiàn)?Dijkstra?最短路算法的方法
這篇文章主要教你在?Java?中實(shí)現(xiàn)?Dijkstra?最短路算法的方法,在實(shí)現(xiàn)最短路算法之前需要先實(shí)現(xiàn)帶權(quán)有向圖,文章中給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04詳解SSM框架下結(jié)合log4j、slf4j打印日志
本篇文章主要介紹了詳解SSM框架下結(jié)合log4j、slf4j打印日志,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11一文詳解Redisson分布式鎖底層實(shí)現(xiàn)原理
這篇文章主要詳細(xì)介紹了Redisson分布式鎖底層實(shí)現(xiàn)原理,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07Struts2中ognl遍歷數(shù)組,list和map方法詳解
這篇文章主要介紹了Struts2中ognl遍歷數(shù)組,list和map方法詳解,需要的朋友可以參考下。2017-09-09Mybatis-Plus使用updateById()、update()將字段更新為null
本文主要介紹了Mybatis-Plus使用updateById()、update()將字段更新為null,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08