本系列文章旨在記錄和總結自己在Java Web開發之路上的知識點、經驗、問題和思考,原來已經分享在我的CSDN博客,現在分享在頭條,希望能幫助更多碼農和想成為碼農的人。版權聲明:本文為CSDN博主「普通的碼農」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。原文鏈接:https://blog.csdn.net/liyongyan1202/article/details/89064944
前面的文章多次使用到了HTML(HyperText Markup Language,中文就是超文本標記語言)。這門語言可以使用任何的文本編輯器進行編寫,寫出的文檔就是網頁,只要將文件名以后綴 .html 結尾,瀏覽器就可以解釋該文檔,并以一定的格式呈現出來。
HTML是Web前端三劍客之一,主要是負責數據的結構、框架或骨架,表示哪些數據是標題啊、主體啊、導航啊、鏈接啊、表格啊、段落啊、圖片啊、音頻、代碼啊等。簡而言之,就是表示數據是什么。
HTML的核心思想很簡單,就是給你的信息打標記,舉個例子:
<這是一篇文章> <這是標題>XX爆炸性新聞</這是標題> <這是段落> 某年某月某日,某某發生某事。。。 </這是段落> <這是段落> 其他事情。。。 </這是段落> </這是一篇文章>
尖括號 < > 及其里面的詞就是標記或者標簽,只不過HTML標準里面用的是英文單詞,我這只是用中文的詞來說明這個思想。
標記有開始標記和結束標記,結束標記里面多一個正斜杠,就是 </ > 。
真正的信息(就是要呈現給用戶看的)就寫在開始標記和結束標記之間,這就是標記的內容。有時候沒有內容,開始標記和結束標記可以合二為一,變成 < /> ,就是把正斜杠寫到右尖括號前面,或者省略正斜杠。
標記及其內容合起來叫做元素,比如例子中的開始標記<文章>和結束標記</文章>及其之間的內容就是一個元素。可以看到元素可以嵌套,就是元素里面的內容可以再次包含元素,不過開始標記和結束標記要注意遙相呼應,事實上,編寫的時候可以采用縮進來增加層次感且不易出錯。
元素還有屬性,屬性可以有屬性值,也可以沒有,這些后面再討論。這里要提到的是一個編寫規范,不管是標記、屬性還是值,習慣上都采用英文小寫單詞用連字符(就是短橫線、減號)相連。表單中的需要發往Web服務器的數據可以使用后端的開發規范。
每一個元素都可以設置一個id屬性,其值必須在該網頁中是唯一的。
目前HTML的版本是HTML5,大多數瀏覽器版本都支持大部分常用的特性。再次強調,HTML的主要思想就是給你的信息打標記,這些標記表示數據的結構、框架或骨架,就是語義,至于數據如何呈現(比如呈現在哪個位置,什么顏色、字體、背景等等)和動態行為是由CSS(層疊樣式表,Cascading Style Sheets)和JavaScript來負責。它們都由瀏覽器來解釋執行。
HTML5的網頁基本都有如下結構:
<!DOCTYPE html> <html lang="zh-cmn-hans"> <head> <meta charset="utf-8" /> <title>這里是網頁的標題</title> </head> <body> </body> </html>
可以看到,基本結構就是由各種標記組成的,只不過標記使用的是英文單詞,而且這些標記都是標準化(這樣各家瀏覽器都能識別)了的,瀏覽器必須識別它們。這些標記也很語義化(這樣人類能容易識別,便于開發和維護網頁),基本上都是使用語義相對應的英文單詞或縮寫,這也是理所當然的,便于記憶和使用嘛。
基本結構里面包含以下幾點:
但是,僅僅告訴瀏覽器使用何種字符編碼還不行,網頁文檔本身在使用文本編輯器編寫完保存的時候,必須使用該字符編碼來保存;通過網絡傳輸的話,那傳輸時也必須使用該字符編碼來傳輸。
就好像我給你寫了一封信,信上指示你說要用英語來讀這封信,但信的內容卻是用中文寫的,那么你還是不能讀這封信。這里我使用了普遍采用的utf-8這種字符編碼,Web上基本都用這個,它能表示全球各個語言中的字符,而且占用字節數較少。字符編碼也是比較復雜但很重要的內容,這里暫且不說;
OK,每個網頁都有這個基本結構,剩下的就是往<body>元素里面添加你的各種信息了。
首先,我們可以向<body>元素里面添加文本,畢竟文字在我們的生活中占據很重要的位置,特別是在古代。文字可以寫成小說、劇本、新聞、資訊等等。添加文本很簡單,直接在元素里面敲文字(各種語言都行)就可以了。
如果光是這樣的話,那就談不上說HTML負責數據的結構、框架或骨架了。不錯,HTML還提供了很多標記來描述數據,這里先說一些常用的。
寫文章的時候通常要為文章起標題,而且有文章的總標題,副標題,文章內容相關的放在同一個標題下,標題下又可能有若干個子標題,就類似文檔的大綱似的。所以,HTML提供了<h1>、<h2>、<h3>、<h4>、<h5>、<h6>這六個標記來說明標題的語義。我們試試給信息打上這些標記會是什么樣子,先看代碼:
<!DOCTYPE html> <html lang="zh-cmn-hans"> <head> <meta charset="utf-8" /> <title>這里是網頁的標題</title> </head> <body> <h1>這是一級標題</h1><h2>這是一級標題</h2> <h3>這是一級標題</h3><h4>這是一級標題</h4> <h5>這是一級標題</h5><h6>這是一級標題</h6> </body> </html>
我特地把這些信息兩個一行來寫,看看瀏覽器是怎么處理的:
可以看到,瀏覽器會把各級標題的字體大小顯示的不太一樣,一級標題最大,六級標題最小。大多數瀏覽器都會這么處理。
那么問題來了,不是說HTML只負責信息的語義,不負責信息是如何展示的嗎?話雖如此,給不同語義的信息來個默認的展示效果,不就省的我們還要再寫信息如何展示的代碼了嘛。這又再一次體現了契約優先(本質就是由默認值)的思想。這就是說我們還是可以修改這些各級標題是如何展示的,比如字體、顏色等等,這就要用到CSS了。
注意,瀏覽器可以關閉CSS和JavaScript,就是讓這兩個技術不起作用,從這個角度看,給標記設置默認的展示效果也有這方面的原因。
雖然標記有默認的展示效果,但我們一定不能為了獲得這些類似的展示效果而強行給某些信息打上標記,而是應該使用CSS去獲得這些展示效果。比如,不能為了加大字體就強行把不是標題的內容打上<h1>標記,這應該交給CSS去做,是否打上<hn>標記(n代表1、2、3、4、5、6)應該考慮該內容在語義上是否是標題!
第二點很重要的是,明明代碼里面兩個標題一行,為何瀏覽器展示的效果確是每個標題占一行?
這是因為HTML規定了某些標記有個作用,瀏覽器遇到它就必須另起一行來展示,有這個作用的標記就叫塊級標記,沒有這個作用的就是行級標記(不知道是否可以這么劃分,通常的劃分方式是塊級標記是里面可以嵌套其他標記的,行級標記是里面不可以嵌套其他標記的)。
事實上,HTML規定文本里面的格式(縮進、換行、多個連續的空格等等)都沒用,瀏覽器會把這些格式壓縮成一個空格。比如下面的代碼實際展示的是一行:
<body> 這是第一段。。。 這是第二段。。。 這是第三段。。。 </body>
展示效果成了:
這是第一段。。。 這是第二段。。。 這是第三段。。。
除了表示標題的標記以外,還有其他很多作用于文本,甚至是整個語義上的布局的標記,下面僅僅使用一段代碼來展示比較常用的標記,不做過多解釋說明。
<!DOCTYPE html> <html lang="zh-cmn-hans"> <head> <meta charset="utf-8" /> <title>這里是網頁的標題</title> </head> <body> <header> <h1>這里是網頁的頁眉,里面可以有導航</h1> <nav> 這里是整個頁面的導航按鈕。。。 </nav> </header> <main><!-- 一個頁面往往只有一個主體部分 --> <article><!-- 這里也可以使用通用容器標記<div> --> <header> <h2>這里是主體的頁眉,可以有主體的導航/目錄、介紹等等</h2> <nav> 這里是目錄 </nav> <p>這里是介紹</p> </header> <article> <h2>這里是第一篇文章</h2> <p>這里是段落一</p> <p>這里是段落二</p> <p>。。。</p> </article> <article> <h2>這里是第二篇文章</h2> <section> <h3>這里是第二篇文章的第一部分</h3> <p>與上面類似</p> </section> <section> <h3>這里是第二篇文章的第二部分</h3> <p>與上面類似</p> </section> </article> </article> </main> <aside> <h2>這里是附注,往往跟主體內容相關性沒那么強,但比較獨立,瀏覽器上通常會顯示主體的兩側</h2> </aside> <footer> <p>這里是整個頁面的頁腳,通常放一些版權聲明、隱私政策之類的。</p> </footer> </body> </html>
涉及到布局的標記有:
涉及到文本內容的標記有:
還有很多用來標記文本的,比如表示重要性的<strong>、表示強調的<em>、表示圖的<figure>和<figcaption>、表示引用或參考的<cite>、表示引述文本的<blockquote>和<q>、表示代碼的<code>等等。
以上這些標記在我們的Java Web開發中暫時用不到,所以都不討論。
鏈接可以說是網頁的靈魂,正是它才能形成Web。表示信息是一個鏈接的標記是<a>。下面舉例:
<!DOCTYPE html> <html lang="zh-cmn-hans"> <head> <meta charset="utf-8" /> <title>這里是網頁的標題</title> </head> <body> <nav> <ul> <li><a >CSDN</a></li> <li><a >百度</a></li> </ul> </nav> <p> 這里是某些文本。。。有些<a href="dir/file.html">東西</a>需要人們在另一個網頁中查看 </p> <nav> <ul> <li><a href="#question1">What is HTML?</a></li> <li><a href="#question2">How does HTML work?</a></li> </ul> </nav> <article> <h1 id="question1">What is HTML?</h1> <p> HTML is ... <br /><br /><br /><br /><br /><br /><br /><br /><br /> <br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /> </p> <h2 id="question2">How does HTML work?</h2> <p> HTML is explained by browser ... </p> </article> <p></p> </body> </html>
在瀏覽器上呈現這樣:
上面的例子中<ul>標記表示是一個無序列表,<li>表示是列表中的一項。我們將CSDN、百度和東西打上<a>標記,表示它們都是一個鏈接,用鼠標點擊它們可以看到指定的另一個網頁。<br>表示需要換行,這里用來模擬頁面很長導致看不見question2。
可以看到,瀏覽器為列表、鏈接都設置了默認的樣式,鼠標移到鏈接上會變成手型。
鏈接必須要有一個href屬性,其值就是目標網頁的URL,當然可以是本網站內部的其他網頁,也可以是外部網站某個網頁,甚至是本網頁中的其他部分(需要使用元素的id屬性,如例子所示)。
表單允許用戶向Web服務器提交數據,而不是僅僅用瀏覽器向Web服務器索要網頁。
最常用的表單應該要數登錄表單了。用戶通過輸入用戶名和密碼,點擊登錄按鈕向Web服務器發起登錄請求。
表單使用標記<form>,在<form>添加供用戶輸入信息的其他內容,常用的有文本框、密碼框、單選按鈕、復選按鈕、下拉列表選擇框以及最后的提交按鈕。我們先簡單介紹文本框、密碼框以及提交按鈕,其他的以后再介紹。
先上代碼:
<!DOCTYPE html> <html lang="zh-cmn-hans"> <head> <meta charset="utf-8" /> <title>這里是網頁的標題</title> </head> <body> <form method="post" action="login-success.html"> <label for="user-name">用戶名:</label><input type="text" id="user-name" name="userName" /> <label for="password">密碼:</label><input type="password" id="password" name="password" /> <input type="submit" value="登錄"/> </form> </body> </html>
再看看瀏覽器上的呈現:
第一點,從呈現上看,用戶名和密碼都是文本框,不過,密碼的文本框會將輸入信息屏蔽,<input>標記使用type屬性來區分各類輸入方式:text是文本框、password是密碼框、radio是單選按鈕、checkbox是復選按鈕等。
其次,我為標記指定了id和name兩個屬性,id屬性用于<label>標記的for屬性,從而將兩個元素關聯起來,這樣,鼠標如果點擊其內容,其關聯的元素也會獲得焦點(就是在文本框內出現了不斷閃爍的輸入光標)。
而name屬性用于標識用戶輸入的該項數據,這樣Web服務器才能根據該名字取出對應的數據,可以理解為Web服務器應用程序中的變量名,或者參數名。
所以,name屬性的值通常遵循后端應用程序的開發規范,由于我們以后是使用Java Servlet技術作為后端應用的開發技術(還有PHP、Python、Go等技術),所以遵循Java的變量名命名規范(采用駝峰形式,比如userName)。
通常,表單中的每一項輸入都會有一個關聯的<label>元素,用于呈現,總不能光出現一個文本框在那吧,這樣用戶如何知道輸入什么數據呢!它有一個for屬性,用來指定關聯元素的id。
提交按鈕也是使用<input>標記,type屬性值是submit,還有一個value屬性,用來指定按鈕上呈現的文字。
最后,回過頭來看<form>標記,它必須指定一個action屬性,它的值通常是后端應用中處理用戶提交數據的一個程序的URL,比如可以是login.servlet、login.jsp、login.php、login.asp等等,后綴名通常表明了采用的后端應用開發技術。當然,我的例子里直接指定了一個HTML頁面(login-success.html,你需要創建這么一個頁面,相信你已經知道怎么創建了!),相當于不處理用戶提交的數據,一般不能這么做!
那么,用戶提交的數據是如何提交到Web服務器的呢?答案當然就是HTTP啊,瀏覽器使用HTTP來向Web服務器請求網頁,再用它來向Web服務器提交數據也就理所當然了啊。瀏覽器會將用戶輸入的信息封裝成HTTP的報文格式,最后通過網卡發送出去。
在這篇文章提到過,HTTP報文里面含有HTTP方法,常用的有GET、POST等。<form>標記的method屬性就是用于指定提交數據的HTTP方法。默認值是get,但通常使用post,一方面get的語義就是獲取而不是提交。另一方面get會將表單數據作為參數直接暴露在瀏覽器的地址欄里面。最后get提交的數據不能太大。
我們把上述代碼中的元素的method屬性值修改為get,保存,然后用瀏覽器打開,輸入用戶名和密碼后點擊提交,效果呈現如下:
可以看到,使用GET方法提交表單數據會在URL上附加上你的數據,同時也可以看到<input>標記的name屬性值在這所起的作用,就是參數名的作用。
到此為止,我們就先介紹這么多,我們來總結一下:
習是一件很痛苦的事情,很多人們總問別人有沒有捷徑,問學習路線,好像問完了就學會了一樣。其實我想說是:要是你真的想做一件事,那么就立刻去做好了,因為時間是不會等你的,在你猶豫的時候,時間早就流走了。所以與其猶豫不決不如理科開始行!
有的人說學Java要先學HTML,那就一起來看HTML是什么吧!
首先HTML 并不是一種編程語言,而是一種標記語超文本標記語言,負責展示網站的外觀,用來控制各種屬性的展示,要想做JavaWeb開發,HTML是必須學會的基礎。
既然要學習開發,那么必須要有工具呀!
EditPlus 3
嗯!就先來認識一下HTML的一些標簽
其中的主要的標簽如下
<html>-------------------開始html文檔
<head>--------------------開始文檔頭部
<title>---------------------開始文檔標題
This is first page
</title>---------------------結束文檔標題
</head>----------------------結束文檔頭部
<body>-----------------------開始文檔體
Hello World.
</body>------------------------結束文檔體
</html>------------------------結束html文檔
新建 HTML 頁面就會出現代碼如下(綠色文字為注釋):
一個頁面的整體結構就是:
所有的內容都在<html></html>這兩個標簽內部。然后分為 <head> </head>頭部和 <body> </body>身體兩部分。
文檔的頭部描述了文檔的各種屬性和信息,包括文檔的標題、在 Web 中的位置以及和其他文檔的關系等。
絕大多數文檔頭部包含的數據都不會真正作為內容顯示給讀者。
<body>標簽包含文檔的所有內容
(比如文本、超鏈接、圖像、表格和列表等等。)
<title> 定義文檔的標題。
瀏覽器會以特殊的方式來使用標題,并且通常把它放置在瀏覽器窗口的標題欄或狀態欄上。同樣,當把文檔加入用戶的鏈接列表或者收藏夾或書簽列表時,標題將成為該文檔鏈接的默認名稱。
下面的就算是一個簡單的網頁了。
讓我們保存執行一下看看。
hello world!Java學習我來了!
要: 響應式宣言如何解讀,Java中如何進行響應式編程,Reactor Streams又該如何使用?熱衷于整合框架與開發工具的阿里云技術專家杜萬,為大家全面解讀響應式編程,分享Spring Webflux的實踐。
本篇文章來自于2018年12月22日舉辦的《阿里云棲開發者沙龍—Java技術專場》,杜萬專家是該專場第四位演講的嘉賓,本篇文章是根據杜萬專家在《阿里云棲開發者沙龍—Java技術專場》的演講視頻以及PPT整理而成。
摘要:響應式宣言如何解讀,Java中如何進行響應式編程,Reactor Streams又該如何使用?熱衷于整合框架與開發工具的阿里云技術專家杜萬,為大家全面解讀響應式編程,分享Spring Webflux的實踐。從響應式理解,到Reactor項目示例,再到Spring Webflux框架解讀,本文帶你進入Java響應式編程。
演講嘉賓簡介:
杜萬(倚賢),阿里云技術專家,全棧工程師,從事了12年 Java 語言為主的軟件開發工作,熱衷于整合框架與開發工具,Linux擁躉,問題終結者。合作翻譯《Elixir 程序設計》。目前負責阿里云函數計算的工具鏈開發,正在實踐 WebFlux 和 Reactor 開發新的 Web 應用。
本次直播視頻精彩回顧,戳這里!https://yq.aliyun.com/live/721
PPT下載地址:https://yq.aliyun.com/download/3187
以下內容根據演講嘉賓視頻分享以及PPT整理而成。
本文圍繞以下三部分進行介紹:
1.Reactive
2.Project Reactor
3.Spring Webflux
一.Reactive
1.Reactive Manifesto
下圖是Reactive Manifesto官方網站上的介紹,這篇文章非常短但也非常精悍,非常值得大家去認真閱讀。
響應式宣言是一份構建現代云擴展架構的處方。這個框架主要使用消息驅動的方法來構建系統,在形式上可以達到彈性和韌性,最后可以產生響應性的價值。所謂彈性和韌性,通俗來說就像是橡皮筋,彈性是指橡皮筋可以拉長,而韌性指在拉長后可以縮回原樣。這里為大家一一解讀其中的關鍵詞:
1)響應性:快速/一致的響應時間。假設在有500個并發操作時,響應時間為1s,那么并發操作增長至5萬時,響應時間也應控制在1s左右。快速一致的響應時間才能給予用戶信心,是系統設計的追求。
2)韌性:復制/遏制/隔絕/委托。當某個模塊出現問題時,需要將這個問題控制在一定范圍內,這便需要使用隔絕的技術,避免連鎖性問題的發生。或是將出現故障部分的任務委托給其他模塊。韌性主要是系統對錯誤的容忍。
3)彈性:無競爭點或中心瓶頸/分片/擴展。如果沒有狀態的話,就進行水平擴展,如果存在狀態,就使用分片技術,將數據分至不同的機器上。
4)消息驅動:異步/松耦合/隔絕/地址透明/錯誤作為消息/背壓/無阻塞。消息驅動是實現上述三項的技術支撐。其中,地址透明有很多方法。例如DNS提供的一串人類能讀懂的地址,而不是IP,這是一種不依賴于實現,而依賴于聲明的設計。再例如k8s每個service后會有多個Pod,依賴一個虛擬的服務而不是某一個真實的實例,從何實現調用1 個或調用n個服務實例對于對調用方無感知,這是為分片或擴展做了準備。錯誤作為消息,這在Java中是不太常見的,Java中通常將錯誤直接作為異常拋出,而在響應式中,錯誤也是一種消息,和普通消息地位一致,這和JavaScript中的Promise類似。背壓是指當上游向下游推送數據時,可能下游承受能力不足導致問題,一個經典的比喻是就像用消防水龍頭解渴。因此下游需要向上游聲明每次只能接受大約多少量的數據,當接受完畢再次向上游申請數據傳輸。這便轉換成是下游向上游申請數據,而不是上游向下游推送數據。無阻塞是通過no-blocking IO提供更高的多線程切換效率。
2.Reactive Programming
響應式編程是一種聲明式編程范型。下圖中左側顯示了一個命令式編程,相信大家都比較熟悉。先聲明兩個變量,然后進行賦值,讓兩個變量相加,得到相加的結果。但接著當修改了最早聲明的兩個變量的值后,sum的值不會因此產生變化。而在Java 9 Flow中,按相同的思路實現上述處理流程,當初始變量的值變化,最后結果的值也同步發生變化,這就是響應式編程。這相當于聲明了一個公式,輸出值會隨著輸入值而同步變化。
響應式編程也是一種非阻塞的異步編程。下圖是用reactor.ipc.netty實現的TCP通信。常見的server中會用循環發數據后,在循環外取出,但在下圖的實現中沒有,因為這不是使用阻塞模型實現,是基于非阻塞的異步編程實現。
響應式編程是一種數據流編程,關注于數據流而不是控制流。下圖中,首先當頁面出現點擊操作時產生一個click stream,然后頁面會將250ms內的clickStream緩存,如此實現了一個歸組過程。然后再進行map操作,得到每個list的長度,篩選出長度大于2的,這便可以得出多次點擊操作的流。這種方法應用非常廣泛,例如可以篩選出雙擊操作。由此可見,這種編程方式是一種數據流編程,而不是if else的控制流編程。
之前有提及消息驅動,那么消息驅動(Message-driven)和事件驅動(Event-driven)有什么區別呢。
1)消息驅動有確定的目標,一定會有消息的接受者,而事件驅動是一件事情希望被觀察到,觀察者是誰無關緊要。消息驅動系統關注消息的接受者,事件驅動系統關注事件源。
2)在一個使用響應式編程實現的響應式系統中,消息擅長于通訊,事件擅長于反應事實。
3.Reactive Streams
Reactive Streams提供了一套非阻塞背壓的異步流處理標準,主要應用在JVM、JavaScript和網絡協議工作中。通俗來說,它定義了一套響應式編程的標準。在Java中,有4個Reactive Streams API,如下圖所示:
這個API中定義了Publisher,即事件的發生源,它只有一個subscribe方法。其中的Subscriber就是訂閱消息的對象。
作為訂閱者,有四個方法。onSubscribe會在每次接收消息時調用,得到的數據都會經過onNext方法。onError方法會在出現問題時調用,Throwable即是出現的錯誤消息。在結束時調用onComplete方法。
Subscription接口用來描述每個訂閱的消息。request方法用來向上游索要指定個數的消息,cancel方法用于取消上游的數據推送,不再接受消息。
Processor接口繼承了Subscriber和Publisher,它既是消息的發生者也是消息的訂閱者。這是發生者和訂閱者間的過渡橋梁,負責一些中間轉換的處理。
Reactor Library從開始到現在已經歷經多代。第0代就是java包Observable 接口,也就是觀察者模式。具體的發展見下圖:
第四代雖然仍然是RxJava2,但是相比第三代的RxJava2,其中的小版本有了不一樣的改進,出現了新特性。
Reactor Library主要有兩點特性。一是基于回調(callback-based),在事件源附加回調函數,并在事件通過數據流鏈時被調用;二是聲明式編程(Declarative),很多函數處理業務類似,例如map/filter/fold等,這些操作被類庫固化后便可以使用聲明式方法,以在程序中快速便捷使用。在生產者、訂閱者都定義后,聲明式方法便可以用來實現中間處理者。
二.Project Reactor
Project Reactor,實現了完全非阻塞,并且基于網絡HTTP/TCP/UDP等的背壓,即數據傳輸上游為網絡層協議時,通過遠程調用也可以實現背壓。同時,它還實現了Reactive Streams API和Reactive Extensions,以及支持Java 8 functional API/Completable Future/Stream /Duration等各新特性。下圖所示為Reactor的一個示例:
首先定義了一個words的數組,然后使用flatMap做映射,再將每個詞和s做連接,得出的結果和另一個等長的序列進行一個zipWith操作,最后打印結果。這和Java 8 Stream非常類似,但仍存在一些區別:
1)Stream是pull-based,下游從上游拉數據的過程,它會有中間操作例如map和reduce,和終止操作例如collect等,只有在終止操作時才會真正的拉取數據。Reactive是push-based,可以先將整個處理數據量構造完成,然后向其中填充數據,在出口處可以取出轉換結果。
2)Stream只能使用一次,因為它是pull-based操作,拉取一次之后源頭不能更改。但Reactive可以使用多次,因為push-based操作像是一個數據加工廠,只要填充數據就可以一直產出。
3)Stream#parallel()使用fork-join并發,就是將每一個大任務一直拆分至指定大小顆粒的小任務,每個小任務可以在不同的線程中執行,這種多線程模型符合了它的多核特性。Reactive使用Event loop,用一個單線程不停的做循環,每個循環處理有限的數據直至處理完成。
在上例中,大家可以看到很多Reactive的操作符,例如flatMap/concatWith/zipWith等,這樣的操作符有300多個,這可能是學習這個框架最大的壓力。如何理解如此繁多的操作符,可能一個歸類會有所幫助:
1)新序列創建,例如創建數組類序列等;
2)現有序列轉換,將其轉換為新的序列,例如常見的map操作;
3)從現有的序列取出某些元素;
4)序列過濾;
5)序列異常處理。
6)與時間相關的操作,例如某個序列是由時間觸發器定期發起事件;
7)序列分割;
8)序列拉至同步世界,不是所有的框架都支持異步,再需要和同步操作進行交互時就需要這種處理。
上述300+操作符都有如下所示的彈珠圖(Marble Diagrams),用表意的方式解釋其作用。例如下圖的操作符是指,隨著時間推移,逐個產生了6個元素的序列,黑色豎線表示新元素產生終止。在這個操作符的作用下,下方只取了前三個元素,到第四個元素就不取了。這些彈珠圖大家可以自行了解。
三.Spring Webflux
1.Spring Webflux框架
Spring Boot 2.0相較之前的版本,在基于Spring Framework 5的構建添加了新模塊Webflux,將默認的web服務器改為Netty,支持Reactive應用,并且Webflux默認運行在Netty上。而Spring Framework 5也有了一些變化。Java版本最低依賴Java 8,支持Java 9和Java 10,提供許多支持Reactive的基礎設施,提供面向Netty等運行時環境的適配器,新增Webflux模塊(集成的是Reactor 3.x)。下圖所示為Webflux的框架:
左側是通常使用的框架,通過Servlet API的規范和Container進行交互,上一層是Spring-Webmvc,再上一層則是經常使用的一些注解。右側為對應的Webflux層級,只要是支持NIO的Container,例如Tomcat,Jetty,Netty或Undertow都可以實現。在協議層的是HTTP/Reactive Streams。再上一層是Spring-Webflux,為了保持兼容性,它支持這些常用的注解,同時也有一套新的語法規則Router Functions。下圖顯示了一個調用的實例:
在Client端,首先創建一個WebClient,調用其get方法,寫入URL,接收格式為APPLICATION_STREAM_JSON的數據,retrieve獲得數據,取得數據后用bodyToFlux將數據轉換為Car類型的對象,在doOnNext中打印構造好的Car對象,block方法意思是直到回調函數被執行才可以結束。在Server端,在指定的path中進行get操作,produces和以前不同,這里是application/stream+json,然后返回Flux范型的Car對象。傳統意義上,如果數據中有一萬條數據,那么便直接返回一萬條數據,但在這個示例返回的Flux范型中,是不包含數據的,但在數據庫也支持Reactive的情況下,request可以一直往下傳遞,響應式的批量返回。傳統方式這樣的查詢很有可能是一個全表遍歷,這會需要較多資源和時間,甚至影響其他任務的執行。而響應式的方法除了可以避免這種情況,還可以讓用戶在第一時間看到數據而不是等待數據采集完畢,這在架構體驗的完整性上有了很大的提升。application/stream+json也是可以讓前端識別出,這些數據是分批響應式傳遞,而不會等待傳完才顯示。
現在的Java web應用可以使用Servlet棧或Reactive棧。Servlet棧已經有很久的使用歷史了,而現在又增加了更有優勢的Reactive棧,大家可以嘗試實現更好的用戶體驗。
2.Reactive編程模型
下圖中是Spring實現的一個向后兼容模型,可以使用annotation來標注Container。這是一個非常清晰、支持非常細節化的模型,也非常利于同事間的交流溝通。
下圖是一個Functional編程模型,通過寫函數的方式構造。例如下圖中傳入一個Request,返回Response,通過函數的方法重點關注輸入輸出,不需要區分狀態。然后將這些函數注冊至Route。這個模型和Node.js非常接近,也利于使用。
3.Spring Data框架
Spring Data框架支持多種數據庫,如下圖所示,最常用的是JPA和JDBC。在實踐中,不同的語言訪問不同的數據庫時,訪問接口是不一樣的,這對編程人員來說是個很大的工作量。
Spring Data便是做了另一層抽象,使你無論使用哪種數據庫,都可以使用同一個接口。具體特性這里不做詳談。
下圖展示了一個Spring Data的使用示例。只需要寫一個方法簽名,然后注解為Query,這個方法不需要實現,因為框架后臺已經采用一些技術,直接根據findByFirstnameAndLastname就可以查詢到。這種一致的調用方式無疑提供了巨大的方便。
現在Reactive對Spring Data的支持還是不完整的,只支持了MongoDB/Redis/Cassandra和Couchbase,對JPA/LDAP/Elasticsearch/Neo4j/Solr等還不兼容。但也不是不能使用,例如對JDBC數據庫,將其轉為同步即可使用,重點在于findAll和async兩個函數,這里不再展開詳述,具體代碼如下圖所示:
Reactive不支持JDBC最根本的原因是,JDBC不是non-blocking設計。但是現在JavaOne已經在2016年9月宣布了Non-blocking JDBC API的草案,雖然還未得到Java 10的支持,但可見這已經成為一種趨勢。
四.總結
Spring MVC框架是一個命令式邏輯,方便編寫和調試。Spring WebFlux也具有眾多優勢,但調試卻不太容易,因為它經常需要切換線程執行,出現錯誤的棧可能已經銷毀。當然這也是現今Java的編譯工具對WebFlux不太友好,相信以后會改善。下圖中列出了Spring MVC和Spring WebFlux各自的特性及交叉的部分。最后也附上一些參考資料。
作者:李博bluemind
*請認真填寫需求信息,我們會在24小時內與您取得聯系。