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