21, Giới thiệu về Listener
Phần này là trích đoạn một phần tài liệu nhật ký học web với Java Servlet của tôi. Mn có thể tham khảo full ở đây (để link thôi cập nhật sau): https://drive.google.com/drive/folders/1cuyr-hoqBTHh_zNkduHrVNsy0MI0l9VZ?usp=sharing
Preview
- Trước hết nghe tên “Listener”: một bộ máy lắng nghe? Trước đây làm việc với JS tôi có làm quen với EventListener, là một dạng máy nghe như vậy, nằm chờ ở một phần tử nào đó trên web (hoặc toàn bộ web), chờ một sự kiện kích hoạt thì sẽ thực thi một hàm (callback) nào đó. Thế trong Java Servlet là gì?
Định nghĩa
Trước hết hãy xem xét “Event”.
- Event – sự kiện – là một đối tượng đại diện cho một hành động hoặc sự thay đổi trạng thái mà ứng dụng cần xử lý.
- Listener trong Java là một đối tượng được sử dụng để lắng nghe và xử lý các sự kiện (events) trong ứng dụng. Khi một sự kiện xảy ra, Listener sẽ thực thi một phương thức được chỉ định để xử lý sự kiện đó.
- Đơn giản hơn, Event giống như "tín hiệu" thông báo khi có một việc gì đó xảy ra. Listener sẽ đứng "nghe ngóng" sự kiện đó để phản hồi đúng lúc.
Vậy cụ thể hơn: Event có thể là gì, sự kiện kích hoạt là gì, một sự kiện mức nào/dạng nào thì được gọi là Event, Listener làm gì để có thể đứng một chỗ để nghe Event, những phản hồi của Listener có thể là gì? n câu hỏi, bình tĩnh giải thích từng cái một.
Event
- Một Event – một sự kiện – có thể là bất cứ điều gì xảy ra trong chương trình liên quan đến sự thay đổi trạng thái của người dùng hoặc hệ thống.
- Thay đổi của người dùng có thể là họ ấn vào một nút, nhập vào bàn phím, di chuyển chuột,…
- Thay đổi của hệ thống có thể là Session được tạo/hủy, thay đổi trạng thái ServletContext(ứng dụng mở/tắt), khi có một yêu cầu mới tới Server, … đại loại thế.
- Vậy có phải bất cứ thay đổi nào cũng là Event? Một biến thay đổi có được gọi là một Event không?
Về lý thuyết thì có. Bất cứ thứ gì thay đổi cũng có thế coi là một Event. Thậm chí từng cái biến thay đổi cũng có thể coi là Event. Với việc custom thẳng vào class JavaBeans thì chúng ta có lắng nghe được sự kiện thay đổi của từng thuộc tính một trong JavaBeans.
Nhưng hôm nay tôi không nói về Event tổng quát, tôi chỉ nói những Event xảy ra liên quan tới
“những thứ không thể chạm tới bằng code core”.
- Nghĩa là với một thay đổi đơn giản như một attribute trong obj nào đó thì có thể không cần dùng thư viện gì phức tạp để tracking sự thay đổi của attribute đó (thay vào đó dùng Listener tự tạo, Event tự tạo, chỉ logic). Nhưng với thứ như Session hết hạn hay Request được gửi tới Server thì với trình độ Java Core của tôi thì chịu. (thực ra xây cái logic đơn giản cho attribute kia tôi cũng chịu rồi, hơn nữa nếu muốn xây cao hơn đòi hỏi lập trình đa luồng Java, cái mà tôi chả biết tí nào). Nên tôi sẽ nói về những tiện ích mà Java đem lại hơn là tự đi xây dựng một Event nặng như thế.
Listener
- Như ở trên đã nói, Listener là một interface trong Java với tác dụng theo dõi và xử lý các sự kiện. Tất cả các Listener đều triển khai EventListener (một SIÊU interface để các Listener triển khai).
- Có vài loại Listener nổi bật trong phần này cần quan tâm, đều thuộc Listener dành cho Server (liên quan đến xử lý Session và toàm chương trình) như ServletContextListener, HttpSessionListener, ServletRequestListener, ServletContextAttributeListener, HttpSessionAttributeListener,…
Chắc là sẽ viết qua về từng cái một chút.
Từ từ trước khi đi vào từng cái, phải xem Listener và Event làm việc như thế nào đã?
Cách hoạt động của Listener nói chung
Vòng đời của Listener
- Với Listener nói chung, nó sẽ được sinh ra khi application được sinh ra. Cụ thể hơn, application sẽ đọc file cấu hình (web.xml) hay quét Annotation để tìm và khởi tạo Listener.
- Khi đối tượng cần đọc tồn tại hay được sinh ra, application lấy được thông tin đó và báo cho Listener (thực ra là gọi đến method initialized của Listener để mở kết nối từ Listener tới đối tượng đó). Method gì thì tùy loại Listener.
- Khi một sự kiện xảy ra (một thay đổi trong WebContainer), WebContainer tất nhiên sẽ biết sự thay đổi đó. Nó sẽ tạo ra một đối tượng Event để truyền tới Listener. Trong Event có chứa thông tin cụ thể về sự kiện, như đối tượng yêu cầu (request), phiên làm việc (session), hoặc ngữ cảnh (context). (sao WebContainer biết được thay đổi nhỉ, có lẽ tìm hiểu ở dưới)
- Dựa vào sự kiện, WebContainer sẽ kích hoạt một method nào đó trong Listener (tương ứng với sự kiện được gọi). Nó không phải method initialized hay destroyed, mà là một method khác được thiết kế để gọi ra lúc sự kiện tương ứng kích hoạt. Như kiểu “onclick” ấy. Đoán là sẽ phải ghi đè nó trong code mình viết.
- Đối tượng Event không tồn tại lâu, nó chỉ tồn tại đủ lâu để truyền thông tin sự kiện từ Container đến các Listener đã đăng ký. Sau khi xong thì nó có thể bị xóa nếu không còn được tham chiếu. (không biết có cần ghi log mà giữ lại ko nhỉ)
- Listener của loại nào cũng có vòng đời từ khi bắt đầu khởi tạo application đến khi application bị hủy. Nghĩa là nó có vòng đời tương đương với cả một chương trình. Lý do nó được xây dựng như vậy (thì tôi đoán) là do Listener được xây dựng với mục đích lắng nghe sự kiện trong cả quá trình, tức là một mình nó ứng với tất cả những sự kiện xảy ra trong vòng đời của chương trình. Nên nó phải có vòng đời tương đương với cả chương trình.
- Nếu đối tượng đang theo dõi bị hủy hay biến mất thì WebContainer gọi method destroyed của Listener (WebContainer gọi thôi chứ mình biết lúc nào mà gọi) để đóng kết nối, ghi log về việc dừng phiên, nói chung là các bước dọn dẹp.
- Sau khi chương trình hủy hay dừng đột ngột thì Listener mới bị hủy. (chứ không phải hủy đối tượng theo dõi là Listener cũng bị hủy)
Trong một Event có gì?
Trong phần xử lý bên trên, cái mù mờ nhất và cũng là cái mình cần thực hiện code là phần xử lý xem chương trình cần làm gì khi xảy ra sự kiện. Thế thì cần biết cụ thể hơn sự kiện đó là gì, đối tượng Event của sự kiện đó là gì và Listener làm như thế nào để thực thi theo yêu cầu.
Event thì có nhiều loại: ServletRequestEvent, HttpSessionEvent, ServletContextEvent, ServletRequestAttributeEvent, HttpSessionAttributeEvent, ServletContextAttributeEvent, HttpSessionBindingEvent, HttpSessionActivationEvent, FilterEvent. (quá nhiều )))
Tôi tự thấy 4 loại cơ bản nhất là 4 cái: ServletRequestEvent, HttpSessionEvent, HttpSessionAttributeEvent, ServletContextAttributeListener. Nên tôi sẽ nói về 3 cái đó:
ServletRequestEvent
Định nghĩa
- ServletRequestEvent là sự kiện được phát sinh khi có một ServletRequest được tạo hoặc hủy.
- Sự kiện này cho phép application theo dõi và quản lý các Request đến Servlet.
Dữ liệu trong ServletRequestEvent
- ServletRequest: Đối tượng ServletRequest liên quan đến sự kiện. Từ đối tượng này có thể truy cập thông tin của Request đến Servlet đang bị nghe.
Listener tương ứng
ServletRequestListener.
Phương thức của Listener tương ứng
- void requestInitialized(ServletRequestEvent sre): Thực hiện khi một Request mới đến Servlet, thường được sử dụng để thực hiện các hành động cần thiết trước khi Request được xử lý.
- void requestDestroyed(ServletRequestEvent sre): Thực hiện khi request bị hủy, thường được sử dụng để giải phóng tài nguyên, ghi log hoặc thực hiện các hành động dọn dẹp khác, hay chỉ thông báo thôi cũng được.
HttpSessionEvent
Định nghĩa
- HttpSessionEvent là sự kiện phát sinh khi một đối tượng HttpSession được tạo hoặc bị hủy.
- Sự kiện này cho phép ứng dụng web theo dõi và quản lý vòng đời của một session.
Dữ liệu trong HttpSessionEvent
- HttpSession: Đối tượng HttpSession liên quan đến sự kiện. Từ đối tượng này, có thể truy cập thông tin về session như ID của session, các thuộc tính được lưu trữ trong session, và trạng thái của session.
Listener tương ứng
HttpSessionListener.
Phương thức của Listener tương ứng
- void sessionCreated(HttpSessionEvent se): Thực hiện khi một session mới được tạo. Thường được sử dụng để khởi tạo các tài nguyên liên quan hoặc lưu trữ thông tin ban đầu cho session.
- void sessionDestroyed(HttpSessionEvent se): Thực hiện khi một session bị hủy. Thường được sử dụng để giải phóng tài nguyên liên quan đến session, ghi log, hoặc thực hiện các hành động dọn dẹp khác. Dù session bị hủy mình vẫn có thể lấy ID và thuộc tính của session trong quá trình xử lý sự kiện này.
Chú ý: Trong trường hợp có sự thay đổi hoặc thao tác với dữ liệu bên trong HttpSession, như thêm, sửa, hoặc xóa các thuộc tính, thì HttpSessionListener không bị tác động trực tiếp. HttpSessionListener chỉ phản ứng với việc tạo và hủy Session thôi. Nếu muốn detect tác động bên trong Session thì phải dùng HttpSessionAttributeEvent.
ServletContextAtrributeEvent
Định nghĩa
- ServletContextAttributeEvent là sự kiện phát sinh khi một thuộc tính được thêm, xóa hoặc thay thế trong ServletContext.
- Sự kiện này cho phép ứng dụng theo dõi và quản lý các thay đổi trong các thuộc tính toàn cục của ứng dụng web.
Dữ liệu trong ServletContextAttributeEvent
- ServletContext: Đối tượng ServletContext liên quan đến sự kiện. Từ đối tượng này, có thể truy cập thông tin và tài nguyên dùng chung của ứng dụng web.
- Attribute Name: Tên của thuộc tính được thêm, xóa hoặc thay thế.
- Attribute Value:
- Trong attributeAdded: giá trị của thuộc tính vừa được thêm vào.
- Trong attributeRemoved: giá trị của thuộc tính vừa bị xóa khỏi ServletContext.
- Trong attributeReplaced: giá trị cũ của thuộc tính trước khi bị thay thế bởi giá trị mới.
Listener tương ứng
ServletContextAttributeListener.
Phương thức của Listener tương ứng
- void attributeAdded(ServletContextAttributeEvent event): Thực hiện khi một thuộc tính mới được thêm vào ServletContext.
- void attributeRemoved(ServletContextAttributeEvent event): Thực hiện khi một thuộc tính bị xóa khỏi ServletContext.
- void attributeReplaced(ServletContextAttributeEvent event): Thực hiện khi một thuộc tính trong ServletContext bị thay thế.
HttpSessionAttributeEvent
Định nghĩa
- HttpSessionAttributeEvent là sự kiện phát sinh khi một thuộc tính được thêm, xóa hoặc thay thế trong một HttpSession.
- Sự kiện này cho phép ứng dụng web theo dõi và quản lý các thay đổi về dữ liệu (thuộc tính) được lưu trữ trong session của từng người dùng.
Dữ liệu trong HttpSessionAttributeEvent
- HttpSession: Đối tượng HttpSession liên quan đến sự kiện. Thông qua HttpSession, có thể truy cập thông tin về session của người dùng.
- Attribute Name: Tên của thuộc tính vừa được thêm, xóa, hoặc thay thế trong session.
- Attribute Value:
- Trong attributeAdded: Giá trị của thuộc tính vừa được thêm vào session.
- Trong attributeRemoved: Giá trị của thuộc tính vừa bị xóa khỏi session.
- Trong attributeReplaced: Giá trị cũ của thuộc tính trước khi bị thay thế bởi giá trị mới.
Listener tương ứng
HttpSessionAttributeListener.
Phương thức của Listener tương ứng
- void attributeAdded(HttpSessionBindingEvent event):
Thực hiện khi một thuộc tính mới được thêm vào HttpSession.
- void attributeRemoved(HttpSessionBindingEvent event):
Thực hiện khi một thuộc tính bị xóa khỏi HttpSession.
- void attributeReplaced(HttpSessionBindingEvent event):
Thực hiện khi một thuộc tính trong HttpSession bị thay thế.
Tại sao chỉ xét 4 cái này cho cơ bản?
Tại sao 4 cái Event này (theo tôi là) quan trọng nhất?
- Một chương trình cần theo dõi các hành động ở mức Request (đơn), Session (nhóm), ServletContext (toàn cục).
- Trong 3 cái trên thì những nơi hay xảy ra sự kiện mà cần bắt nhất (theo tôi là) Request khởi tạo/hủy, Session khởi tạo/hủy, nội dung thay đổi trong Session và ServletContext. Tương đương với 4 cái trên.
Cú pháp code cơ bản
Đó thế thôi ))
Vấn đề tiếp theo tới đâyyyyy!
Hãy để ý rằng: trong cấu trúc Client – Server, Client là người gửi yêu cầu và Server là nơi lắng nghe và phản hồi. Nhưng đã bao giờ Cilent lắng nghe Server chưa?
Nhìn lại mô hình Client – Server ban đầu:
Chưa, chưa bao giờ Client là nơi lắng nghe Server. Client chỉ lắng nghe User và chờ đợi phản hồi từ User. Còn với Server, chỉ khi nào cần dữ liệu hay xử lý thì Client mới để ý xem Server ở đâu và có tồn tại hay không. (Điều này giải thích tại sao khi tắt Netbeans, tắt Tomcat rồi mà Web vẫn hiển thị, chỉ khi nào yêu cầu tác vụ thì mới gây lỗi).
Làm sao để Client quay lại lắng nghe Server và 2 bên cùng lắng nghe nhau đây?
Từ từ nhưng trước hết tại sao tôi lại nói về vấn đề này. Tôi thử code với Listener, tạo một Session và khi Session hủy thì gửi yêu cầu thông báo. Nhưng không được. Listener không hề phát hiện ra sự biến mất của Session. Cho tới khi tôi reload trang thì Listener mới báo lại là không còn Session đó nữa.
Có lẽ nào Listener chỉ ngồi yên đó và hóng thông tin, khi Request mới tới mới lao ra kiểm tra? Như thế thì khác gì Middleware (Filter)? Có phải do ServletContext không bao giờ detect được phần tử trong nó khi nào bị thay đổi không? Như vậy thì lại trái ngược với lý thuyết mới viết bên trên?
Tôi có giả thuyết là Server phụ thuộc hoàn toàn vào phản hồi từ Client, nên đưa ra giả thuyết như trên. Nhưng trong khi đang test lại thêm 1 lần để chứng minh phán đoán thì:
Nhưng sau khi ngồi test lại thì thấy: không phải, nó vẫn detect được, nhưng không giống như thời gian thiết kế. Cụ thể như thế này:
Và Listener:
Set giá trị cho session 1s là hết hạn, nhưng tôi phải đợi tầm 1 phút sau mới có thông báo hủy session. (Điều này làm tôi tưởng nhầm là nó không có phản hồi). Thế là sao?
Vấn đề càng ngày càng rắc rối. Vấn đề cũ về cách hoạt động của Listener chưa được giải quyết, vấn đề mới về việc liên lạc giữa Client và Server lại được tạo ra.
Vậy là những thông tin sau cần được thỏa mãn: ServletContext vẫn detect được phần tử trong nó khi nào bị thay đổi. Có tự động check trạng thái của đối tượng thay đổi, nhưng không phải là check tại thời điểm ngay sau khi object thay đổi (cụ thể là Session hết hạn, đã thể hiện trên Client).
Giả thuyết được đặt ra: việc hủy Session không xảy ra ngay lập tức khi Session hết hạn. Vậy nghĩa là ServletContext sẽ kiểm tra trong thời điểm nào đó xem đối tượng đó đã thay đổi hay chưa. Mở rộng suy nghĩ thêm một chút thì sau mỗi khoảng thời gian, ServletContext sẽ đi kiểm tra một lần các đối tượng và nếu thấy thay đổi thì báo lại cho bên Listener. Vậy vấn đề cần hiểu ở đây là: SERVLETCONTEXT KIỂM TRA THEO CHU KỲ.
Trước đây tôi đọc lý thuyết tôi đã nghĩ: địa chỉ hay giá trị của từng tham số được ServletContainer để ý chặt chẽ, bất cứ thay đổi nào cũng phải ghi log. Nhưng thực chất là: ServletContainer lưu trạng thái qua từng khoảng thời gian, rồi check lại theo chu kỳ. Nếu thấy thay đổi thì sẽ báo ra Listener. Đây mới là bản chất cách hoạt động của Listener.
- Thế có thay đổi được thời gian chu kỳ đó không? Tôi nghĩ là không nên. Đụng vào cài đặt mặc định không phù hợp với cách thực hiện hiện tại của tôi.
- Thế còn vấn đề Client và Server lắng nghe nhau thì sẽ thực hiện như thế nào? Ví dụ như muốn hết thời gian Session là Server gửi phản hồi lên cho Client để thông báo chẳng hạn? đó là một lĩnh vực mới vượt qua phần cơ bản, gọi là Web Socket. Tôi sẽ cố gắng học và nói về nó sau.
Viết thử ví dụ code
Đây là một ví dụ đơn giản, chỉ là tạo một Session, rồi đợi 5s cho Session hết hạn, Listener lắng nghe sự kiện đó rồi thông báo ra output.
Đầu tiên viết Servlet Login:
Sau đó tạo Listener để bắt sự kiện một Session được tạo:
Run thôi:
Output lúc đầu (trước khi Session hết hạn và chu kỳ kiểm tra của ServletContext tới):
Sau đó (Session hết hạn và chu kỳ kiểm tra của ServletContext tới):