版本歷史&代碼示例之:WebSocket、JSTL
前言
你好,我是方同學(xué)(YourBatman)
若你還不太清楚Java EE是什么,可先移步這里:什么是Java EE?
緊接著上一篇講完Servlet、JSP、EL表達(dá)式后,本文嘗試把WebSocket和JSTL再疏通疏通。
所屬專欄
BATutopia-Java EE
相關(guān)下載
- 工程源代碼:https://github.com/yourbatman/BATutopia-java-ee
- 【女媧Knife-Initializr工程】訪問地址:http://152.136.106.14:8761
- Java開發(fā)軟件包(Mac):https://wangpan.yourbatman.cn/s/rEH0 提取碼:javakit
- 程序員專用網(wǎng)盤上線啦,開放注冊送1G超小容量,幫你實踐做減法:https://wangpan.yourbatman.cn
版本約定
Java EE:6、7、8
Jakarta EE:8、9、9.1
正文
WebSocket
WebSocket是一種在單個TCP連接上進(jìn)行全雙工通信的協(xié)議。隨著HTML5的誕生,WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,并由RFC7936補(bǔ)充規(guī)范。WebSocket API也被W3C定為標(biāo)準(zhǔn)。
WebSocket協(xié)議本質(zhì)上是一個基于TCP的協(xié)議,它由通信協(xié)議和編程API組成,WebSocket能夠在瀏覽器和服務(wù)器之間建立雙向連接,以基于事件的方式,賦予瀏覽器實時通信能力。
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù)。在WebSocket API中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。工作流程如下圖:
Java API for WebSocket是Java的Web套接字,在2013年6月份伴隨著Java EE 7推出(1.0版本),Java EE 8升級到1.1版本。
注意:WebSocket的Client可以是瀏覽器,也可是WebSocket的終端(如Java應(yīng)用、Go應(yīng)用)。
- <!-- javax命名空間版本(Tomcat 9.x及以下版本支持) -->
- <dependency>
- <groupId>javax.websocket</groupId>
- <artifactId>javax.websocket-api</artifactId>
- <version>1.1</version>
- <scope>provided</scope>
- </dependency>
- <!-- jakarta命名空間版本(Tomcat 10.x及以上版本支持) -->
- <dependency>
- <groupId>jakarta.websocket</groupId>
- <artifactId>jakarta.websocket-api</artifactId>
- <version>2.0.0</version>
- <!-- <version>1.1.2</version> 此版本命名空間同javax -->
- <scope>provided</scope>
- </dependency>
- 除此之外,一般情況下我們直接使用Web容器提供的Jar即可,如Tomcat
- <dependency>
- <groupId>org.apache.tomcat.embed</groupId>
- <artifactId>tomcat-embed-websocket</artifactId>
- <version>Tomcat版本號</version>
- </dependency>
- <dependency>
- <groupId>org.apache.tomcat</groupId>
- <artifactId>tomcat-websocket</artifactId>
- <version>Tomcat版本號</version>
- </dependency>
版本歷程
servlet-3.1版本開始支持。WebSocket 1.1 版與 1.0 版完全向后兼容,只在javax.websocket.Session中添加了兩個方法:
- <T> void addMessageHandler(Class<T> clazz, MessageHandler.Partial<T> handler) throws IllegalStateException;
- <T> void addMessageHandler(Class<T> clazz, MessageHandler.Whole<T> handler) throws IllegalStateException;
生存現(xiàn)狀
作為Http協(xié)議的“補(bǔ)充”,很好的彌補(bǔ)了其不足,在Web領(lǐng)域?qū)崟r推送,也被稱作Realtime技術(shù)。這種技術(shù)大大提升交互體驗,擁有廣泛的應(yīng)用場景:在線聊天(如web版微信)、在線客服系統(tǒng)、評論系統(tǒng)等等。
總的來講,WebSocket作為新貴,生存現(xiàn)狀挺好,前景一片光明。
實現(xiàn)(框架)
WebSocket其實是構(gòu)建在Http協(xié)議之上的,所以對于Java語言來講它依舊由Web容器來提供實現(xiàn)。
概念區(qū)分:Web容器不一定是Servlet容器,而Servlet容器一定是Web容器
除此之外也有獨(dú)立實現(xiàn):
- client端實現(xiàn):org.eclipse.jetty.websocket:javax-websocket-client-impl
- server端實現(xiàn):org.eclipse.jetty.websocket:javax-websocket-server-impl對于Client來講,一般都是瀏覽器。
代碼示例
前面有提到,WebSocket的Client端既可以是瀏覽器(現(xiàn)代的瀏覽器100%都支持此協(xié)議,若需要考慮瀏覽器兼容問題(比如國外現(xiàn)在依舊有使用老版IE瀏覽器的),可以使用socketio框架哈),也可以是Java應(yīng)用。本示例就加點“難度”,用Java應(yīng)用作為WebSocket的客戶端。當(dāng)然嘍,服務(wù)端肯定也是Java應(yīng)用呀。
創(chuàng)建demo項目,結(jié)構(gòu)如下:
其中client為jar,server為war。
書寫Server端代碼,提供一個服務(wù)端點:
- /**
- * 在此處添加備注信息
- *
- * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
- * @site https://yourbatman.cn
- * @date 2021/9/12 15:29
- * @since 0.0.1
- */
- @ServerEndpoint("/websocket/chat")
- public class WsServer {
- // 當(dāng)前連接上來的連接們(每一個連接都是一個WsServerDemo實例,包含一個Session會話)
- private static Set<WsServer> webSocketSet = new CopyOnWriteArraySet<>();
- // 會話
- private Session session;
- /**
- * 建連成功的回調(diào)
- */
- @OnOpen
- public void onOpen(Session session) {
- this.session = session;
- webSocketSet.add(this); // 保存當(dāng)前連接
- System.out.println("Server有新連接加入!當(dāng)前在線人數(shù)為" + webSocketSet.size());
- }
- /**
- * 連接關(guān)閉調(diào)用的方法
- */
- @OnClose
- public void onClose() {
- webSocketSet.remove(this);
- System.out.println("Server有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + webSocketSet.size());
- }
- /**
- * 收到客戶端消息后調(diào)用的方法
- */
- @OnMessage
- public void onMessage(String message) throws IOException {
- System.out.println("Server來自客戶端的消息:" + message);
- sendMessage("會話[" + session.getId() + "]的消息已經(jīng)收到,內(nèi)容為:" + message);
- // // =======群發(fā)消息=========
- // for (WsServerDemo item : webSocketSet) {
- // try {
- // item.sendMessage(message);
- // } catch (IOException e) {
- // continue;
- // }
- // }
- }
- /**
- * 發(fā)生錯誤時調(diào)用
- */
- @OnError
- public void onError(Throwable error) {
- System.out.println("Server發(fā)生錯誤:" + error.getMessage());
- }
- /**
- * 發(fā)送消息
- */
- public void sendMessage(String message) throws IOException {
- this.session.getBasicRemote().sendText(message);
- }
- }
我這里簡便起見,使用web容器直接實現(xiàn)。有興趣/想深究websocket的同學(xué),可使用org.eclipse.jetty.websocket:javax-websocket-server-impl通過API方式去啟動Server,本文只演示用該方式啟動client哈,二者兼顧嘛。
使用編程方式書寫client端代碼:
- /**
- * 在此處添加備注信息
- *
- * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a>
- * @site https://yourbatman.cn
- * @date 2021/9/12 15:31
- * @since 0.0.1
- */
- @ClientEndpoint
- public class WsClient {
- // 會話(與服務(wù)端建立的會話)
- private Session session;
- /**
- * 建連成功的回調(diào)
- */
- @OnOpen
- public void onOpen(Session session) throws IOException {
- this.session = session;
- System.out.println("Client連接到服務(wù)端成功,會話ID:" + session.getId());
- sendMessage("這是一條來自Client端,會話["+session.getId()+"]的消息");
- }
- @OnMessage
- public void onMessage(String message) {
- System.out.println("Client端收到消息: " + message);
- }
- @OnClose
- public void onClose() {
- System.out.println("Client會話" + session.getId() + "已斷開");
- }
- /**
- * 發(fā)送消息
- */
- public void sendMessage(String message) throws IOException {
- this.session.getBasicRemote().sendText(message);
- }
- }
用main方法啟動Client端,去連接Server端并發(fā)送消息:
- public class ClientApp {
- private static URI uri = URI.create("ws://localhost:8080/websocket/chat");
- private static Session session;
- public static void main(String[] args) throws DeploymentException, IOException, InterruptedException {
- WebSocketContainer container = ContainerProvider.getWebSocketContainer();
- // 順序執(zhí)行5次會話,端口后再建立新會話
- for (int i = 0; i < 5; i++) {
- session = container.connectToServer(WsClient.class, uri);
- session.close();
- TimeUnit.SECONDS.sleep(2);
- }
- }
- }
client端控制臺日志:
- Client連接到服務(wù)端成功,會話ID:1
- Client端收到消息: 會話[0]的消息已經(jīng)收到,內(nèi)容為:這是一條來自Client端,會話[1]的消息
- Client會話1已斷開
- Client連接到服務(wù)端成功,會話ID:2
- Client端收到消息: 會話[1]的消息已經(jīng)收到,內(nèi)容為:這是一條來自Client端,會話[2]的消息
- Client會話2已斷開
- Client連接到服務(wù)端成功,會話ID:3
- Client端收到消息: 會話[2]的消息已經(jīng)收到,內(nèi)容為:這是一條來自Client端,會話[3]的消息
- Client會話3已...
server端控制臺日志:
- Server有新連接加入!當(dāng)前在線人數(shù)為1
- Server來自客戶端的消息:這是一條來自Client端,會話[1]的消息
- Server有一連接關(guān)閉!當(dāng)前在線人數(shù)為0
- Server有新連接加入!當(dāng)前在線人數(shù)為1
- Server來自客戶端的消息:這是一條來自Client端,會話[2]的消息
- Server有一連接關(guān)閉!當(dāng)前在線人數(shù)為0
- Server有新連接加入!當(dāng)前在線人數(shù)為1
- Server來自客戶端的消息:這是一條來自Client端,會話[3]的消息
- Server有一連接關(guān)閉!當(dāng)前在線人數(shù)為0
說明:本文特意使用Java應(yīng)用作為Client端是想讓你更深刻的理解WebSocket的用法,實際場景中,其實大都是B/S模式,通過JavaScript作為客戶端建立連接(相對簡單)。
工程源代碼:https://github.com/yourbatman/BATutopia-java-ee
JSTL
Java server pages standarded tag library,即JSP標(biāo)準(zhǔn)標(biāo)簽庫。主要提供給Java Web開發(fā)人員一個標(biāo)準(zhǔn)通用的標(biāo)簽庫,開發(fā)人員可以利用這些標(biāo)簽取代 JSP頁面上的Java代碼,從而提高程序的可讀性,降低程序的維護(hù)難度。
JSTL強(qiáng)依賴于JSP的存在而存在。
JSTL和EL表達(dá)式的目的是一樣的:取代JSP頁面上寫Java代碼。它比EL更為強(qiáng)大些,可以完成一些結(jié)構(gòu)化邏輯任務(wù),如:迭代、條件判斷、XML文檔操作、國際化、SQL等,下面簡要介紹其主要標(biāo)簽。
- 核心標(biāo)簽:也是著名C標(biāo)簽。在JSP文件開頭引入c標(biāo)簽<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>,支持的常用函數(shù)有:
- 1. <c:out>:用于在頁面輸出
- - <c:out value="expression" default="expression" escapeXml="boolean"/>
- 2. <c:if>:邏輯判斷
- - <c:if test="expression" var="name" scope="scope">
- body content
- </c:if>
- 3. <c:choose>:邏輯判斷。when和otherwise的父標(biāo)簽
- 4. <c:when>: <c:otherwise>:
- - <c:choose>
- <c:when test="expression">
- body content
- </c:when>
- ...
- <c:otherwise>
- body content
- </c:otherwise>
- </c:choose>
- 5. <c:foreach>:
- - <c:forEach var="name" items="expression" varStatus="name" begin="expression" end="expression" step="expression">
- body content
- </c:forEach>
- 6. <c:url>:使用可選的查詢參數(shù)創(chuàng)造一個URL地址
- 7. <c:set>:設(shè)置數(shù)據(jù)
- 8. <c:remove>:刪除數(shù)據(jù)
- 格式化標(biāo)簽:可對數(shù)字、日期時間等格式化。<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>,主要函數(shù):
- 1. <fmt:formatNumber>:格式化數(shù)字
- 2. <fmt:parseNumber>:解析字符串到數(shù)字、貨幣、百分比
- 3. <fmt:formatDate>:
- 4. <fmt:parseData>:
- JSTL函數(shù):一般用于輔助標(biāo)簽控制行為。<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>,主要函數(shù):
- 1. fn:contains:判斷字符串是否包含另外一個字符串
- - <c:if test="${fn:contains(name, searchString)}">
- 2. fn:indexOf:子字符串在母字符串中出現(xiàn)的位置
- - ${fn:indexOf(name, '-')}
- 3. fn:toLowerCase:轉(zhuǎn)為小寫
- - ${fn.toLowerCase(product.name)}
- SQL標(biāo)簽,<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>。主要函數(shù):
- 1. <sql:setDataSource>、<sql:query>、<sql:update>
- XML標(biāo)簽,<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>。主要函數(shù):
- 1. <x:parse>、<x:if>、<x:forEach>
除此之外,還提供了擴(kuò)展點:自定義標(biāo)簽。
- <!-- javax命名空間版本(Tomcat 9.x及以下版本支持) -->
- <dependency>
- <groupId>javax.servlet.jsp.jstl</groupId>
- <artifactId>jstl-api</artifactId>
- <version>1.2</version>
- </dependency>
- <!-- jakarta命名空間版本(Tomcat 10.x及以上版本支持) -->
- <dependency>
- <groupId>jakarta.servlet.jsp.jstl</groupId>
- <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
- <version>2.0.0</version>
- <!-- <version>1.2.7</version> 此版本命名空間同javax -->
- </dependency>
說明:之前可能需要有jstl.jar和standard.jar兩個Jar,但從1.2版本后這一個GAV即可。當(dāng)然嘍,99.99%情況下該jar無需你導(dǎo)入,由web容器負(fù)責(zé)
版本歷程
JSTL也依賴于JSP而存在,所以和JSP版本有強(qiáng)關(guān)系。
JSTL 1.2版本可斷定是最后一個版本,因為JSP已走到盡頭,所以它也會隨之消亡。
生存現(xiàn)狀
同JSP。
實現(xiàn)(框架)
與Servlet相同的Web容器,由Web容器提供解析能力。如tomcat的標(biāo)簽庫實現(xiàn):http://tomcat.apache.org/taglibs
代碼示例
實在沒有應(yīng)用場景了,略。
工程源代碼:https://github.com/yourbatman/BATutopia-java-ee
總結(jié)
WebSocket作為長連接的輕量級解決方案,會是B/S的新寵,一舉替掉之前的長輪訓(xùn)等方案。滾滾長江東逝水,這或許就印證著技術(shù)在進(jìn)步,時代在發(fā)展。
作為老一輩程序員的我,對EL表達(dá)式、JSTL這類技術(shù)依舊有記憶存留,但新時代的程序員可能沒有必要再接觸。本文就當(dāng)做自留地,封存這段學(xué)習(xí)的記憶吧。
本文轉(zhuǎn)載自微信公眾號「BAT的烏托邦」