極*Java速成教程 – (1)

序言

眾所周知,程序員需要快速學習新知識,所以就有了《21天精通C++》和《MySQL-從刪庫到跑路》這樣的書籍,Java作為更“高級”的語言也不應該落後,所以我就寫一個《極·Java速成教程》,讓想學的人能夠快速學習Java(手斜)。
本文大概分三大部分:

  • 編程基礎知識
    • 面向對象思想
    • 數據類型
    • 運算符
    • 流程控制
  • Java語言基礎
  • Java高級特性

本文請逐字理解文本內容,本文僅供理解和學習Java編程,不可作為概念性參考

本文是閱讀《Java編程思想》后的讀後感
本文着重於理解某種思想,所以些許描述顯得晦澀,意會即可

面向對象思想

何為面向對象

Java是一個從設計之初就遵循面向對象思想的語言,因此而有着諸多面向對象的特性,那麼問題來了,面向對象指的是什麼?
面向對象,不是指的臉超女朋友,而是說編程的時候,你所操作的一切事物都是“對象”,假如說Java程序員是個搬磚工,那Java操作的對象就是搬磚工手裡的磚,一塊磚就是一個對象,一堆轉就是一堆對象,諸多同樣的對象可以用一個概念來概括,這個概念就是“”。
先在我們可以思考磚是個什麼東西,也就是磚的“概念(類)”:磚,由土燒制而成,可以用來砌牆、鋪地和墊桌角,有一定的重量和體積,能被搬被砌。仔細分析這句定義,可以發現能分為4小句:

  • 磚是由土燒制而成。也就是說,一個類都是從另一個或多個類繼承發展而來,也就是“繼承(extend)和組合自另一個或多個類”,在Java中,所有類都繼承和擴展自Object類,在自己寫的程序中,一個類可以繼承和擴展自官方的類,別人寫的類和自己寫的類。
  • 磚可以用來砌牆、鋪地和墊桌角。也就是同一個類可以根據不同的需求去加工處理,修改為不同的“形狀”,添加不同的屬性,在不同的地方起到不同的作用。
  • 磚由一定的重量和體積。也就是說每個類都有其屬性,但是對於同一種屬性,他們的值未必相同,正如每個磚的重量和體積都可以不同。
  • 磚能被搬被砌。也就是說每個類都有其功能,屬性是一個類靜態的描述,而功能(或者說方法)則是一個類動態的描述。

將許多個“磚”一樣的對象搭配組合,堆壘到一起,就可以構建起程序的這棟房屋,實現整體的功能。

Java中類的幾個特點

Java中的類有幾個特點

  1. 單根繼承,也就是說某一個類只能直接繼承自另一個類,就像磚只能由土燒制而成,即土→磚,然而除了土,磚的燒制過程中還需要加其他的配料比如外面要燒上去一層琉璃,那就得在磚本身的屬性以上添加一個琉璃的屬性,琉璃的屬性是額外的,獨立無關的,但是他們組合起來實現了更多的功能。也就是說除了直接繼承,還可以添加其他類到一個類中,這種方法叫通過接口(interface)進行繼承。
  2. 訪問控制。對於一個類來說,你只能看到一個類想讓你看到的部分。就像一個磚,你只能看到磚的表面,不能看到它的內里,除非敲開他,但是在Java中,這是不被允許的。一個類想讓你看到的部分和不想讓你看到的部分由不同的關鍵詞進行控制:
    • public 對於所有對象都公開的內容
    • private 只有自己可見的內容
    • protected 只有自己和自己的子孫可以看到的內容
    • default 只有自己的親戚(同一個包)可以看到的內容
      我們不用關心他們內部什麼樣子,我們只需要關心我們需要的,暴露在外面的東西,以及對象能正常工作就行。
  3. “像”就是“是”。對於一個類來說,如果長得像一個東西,那就可以看作是一個東西。就像兒子像父親,父親能幹的兒子也能幹一樣,一塊防火轉是一個磚,那他也有磚的所有屬性,也能被壘起來。而一個具有壘起來方法的對象看上去能壘起來,那他就能像磚一樣被壘起來。
  4. static屬性。對於一個類來說,有的屬性和方法可以是static也就是靜態的,這種屬性和方法不是描述一個類的對象的,而是描述一個類的。就比如可以對所有的磚執行統計數量操作,對於一個磚頭讓他統計同類的數量是不恰當的,但是對於一堆轉,讓這堆磚告訴你有多少塊是可行的。
  5. 對象生命周期管理。Java語言的使用者不用關心Java對象是怎麼誕生的,怎麼消亡的,只需要在需要時new一下,創建一個對象即可,使用完以後的垃圾丟到哪裡,那是Java高級特性需要研究的東西。
  6. Java中的類之上具有包(package)的結構,包是具有層次的,如java.lang.Math就表示java下的lang包下的Math類,這個層次結構可以有好多層,一層一層深入,所以可能這個名字會很長。在使用某一個包中的某一個類的某些內容時,需要使用import進行導入,如果需要導入某個包下的全部,可以使用*通配符。,如java.lang.*。

Java編程需要關心的點

只要想好這個類和它的對象應該怎麼設計,怎麼正常運行即可,其他語言的包袱,甩掉即可

數據類型

一切都是對象,而根基只需要幾個

當Java使用者需要一個對象時,這個對象在內存中的位置時我們不用管也沒法管的。但這個對象是怎麼來的,卻是可以思考的。就像房子是磚壘成的,磚是由土燒成的,那土是什麼構成的呢,這麼追究下去,追究到最後就成了重子和輕子了,但是我們不必追求這麼細,就像Java中我們知道一切對象都有Object這個“祖宗”,但是我們不需要凡事都請動這個祖宗,它有幾個子女是我們常用的,也就是“基本類型”:

類型 大小 包裝器 初始值
boolean true/false Boolean false
char 16 bit Character ‘\u0000’
byte 8 bit Byte (byte)0
short 16 bit Short (short)0
int 32 bit Integer 0
long 64 bit Long 0L
float 32 bit Float 0.0f
double 64 bit Double 0.0d
void Void void

這些基本類型的大小都與其位數n有關,最小值為-2n-1,最大值為2n-1-1,float和double遵循IEEE754標準。
除了基本類型,還有字符串String,高精度整數BigInteger和高精度浮點數BigDecimal等常見類。

還有一些方便的類型可以使用

String

字符串是一個常用的類型,可以用來儲存一個字符序列。字符串類型在內存中是一個不可變對象,也就是說當一個String s="ABC"字符串在內存中創建后,它就不可以被修改了,如果執行了s+="DEF"的操作,那這個s引用實際上是指向了一個新的”ABCDEF”對象,它與原來的”ABC”對象已經沒有關係了。
字符串變量的比較是不可以直接用==判斷的。==是用來進行引用對比的運算符,而兩個內容一樣的字符串引用未必在內存中指向同一個對象。因此,String對象的比較要使用equals()方法進行。

enum

enum是枚舉對象,是一個有限的可以n選1的對象的集合,可以在switch語句內使用。形如:

public enum Spiciness{
    NOT,MILD,MEDIUM,HOT,FLAMING
}

枚舉類型的實例都是常量,可以使用枚舉類型名.枚舉常量的方法選擇常量的值。它內部創建了toString()方法,所以可以很方便地打印枚舉常量的值,還提供了ordinal()方法,可以獲取某個枚舉常量在枚舉類型中的順序。以及靜態的 values()方法,可以按照枚舉常量的聲明順序獲取由這些常量值構成的數組。

對象應當被管理

一個對象,給他個稱號,就可以根據這個名字喊他做各種事,就像家裡人可以喊你小名讓你幹活一樣。但是當出了家裡,可能有好多人跟你重名,這會帶來困擾,因此是不被允許的,而在外面叫你的小名,這也會帶來尷尬,也就是說,這個名字超出了作用域,在Java中,一個稱號(或者說引用)的作用域是從這個命名的聲明到所在花括號的結尾。當然如果不關心這一點也沒問題,因為IDE會提醒你這裏出了問題。
當一個對象有了它的稱呼以後,就可以安排他做好多的事情了,無論誰喊,只要是喊到,就可以用。在作用域範圍內的所有調用都可以通過這個名字實行,而你也可以再給這個對象起另一個名字,這多個名字都是指的同一個對象,當處理不當時,這會帶來一些問題,比如一個喊你張三的人給你圖了個花臉,別人喊你李四把你喊過來以後也會看到一個花臉。
對於一堆相同類型的對象,可以用一個容器裝住他們,方便我們管理和使用,Java中提供了多種類型的容器,有最基本的數組,也就是一串連續的空間去裝一樣的對象,還有其他的諸如set和map之類的容器可以裝,這一點可以在後面進行詳細討論。

運算符

運算符按照字面語義理解就好

運算符有其優先級,也有短路的特徵。優先級就是說先乘除后加減,短路就是邏輯運算時當直接可以產生結果時就不再繼續運算。
運算符有以下幾個:
+。加號,数字的累加,字符串的連接,將較小類型的数字提升為int類型。
-。減號,数字相減。負號。
*。乘號,数字相乘。
/。除號,数字相除,給出結果的整數部分,直接截斷小數,不會四舍五入。
%。求余,数字相除求餘數。
++自增,–自減,當出現在對象前面時先運算再輸出值,當出現在對象後面時先輸出值再運算。
>,<,>=,<=。大於,小於,大於等於,小於等於。顧名思義。
==判斷相等,對於Java來說,這是容易使人困擾的一點,因為除了在判斷基本類型對象是否相等時以外,==僅僅為判斷別名所指向的對象是否為同一個,而不關心對象的屬性是否相等。如果要判斷String等官方封裝好的類型,就要用到equals()方法,但是對於自己定義的類來說,如果沒有重新定義equals()方法,就會和==一樣,對引用對象進行判斷,而不是對對象的屬性進行判斷。
&&,||,!。邏輯運算符與或非,對boolean值進行判斷,兩邊都true時&&結果為真,兩邊有一個為true時||結果為真,!是對boolean對象的邏輯值進行顛倒。
&,|,^,~。按位操作符,對数字類型進行按位的與,或,異或和非操作,與,或,異或是對兩個對象進行操作,非是對一個對象進行操作。
<<,>>,>>>。移位操作符,就是將操作數按位進行移位,A※※B就是將A往箭頭方向移B位。如果是負數,>>在左側補1,>>>在左側補0。
=。数字賦值,或者是給一個對象起別名,或者是將另一個別名添加到一個對象上。
+=,-=,*=,/=,&=,|=,^=,>>=,>>>=,<<=。比較類似,a※=b就是a=a※b。當>>>=byte和short時,轉化成int進行處理然後截斷,可能產生不正確的結果。
指數計數法:AeB代表了Ax10B,如1.39e-43f代表float的1.39×10-43
?:。這是個三元運算符,如A?B:C就表示如果A的邏輯結果為真,就執行B,否則就執行C。
直接常量,就是直接的數,如200D,0x200。其規則是如果後面跟D表示double,F表示float,L表示long,前面跟0表示8進制數,0x表示16進制數。

操作符使用時容易出現的問題

類型轉換

(A)B可以將B強制轉化為A類型,如果是從較小的類型轉化為較大的類型,將不會帶來問題,但如果是從較大的類型轉化為較小的類型,則會帶來信息丟失的問題。自定義類的轉換同理。

截尾和舍入

對於浮點數來說,強制轉化為整形時,會捨棄小數部分,如果要獲取捨入的結果,需要使用java.lang.Math中的round()方法。

流程控制

Java是一種面向對象的語言,但其執行過程依舊時順序的,在順序執行的程序中,會進行一些選擇分支和循環操作。

選擇

Java中可以使用if-else和switch進行分支選擇。

if-else

形如

if(A){
    B
}else{
    C
}

就是如果判斷條件A為真,則執行B,否則執行C。

switch

形如

switch(A){
    case 1: B;
    case 2: C;break;
    case 3: D;break;
    case 4: E;break;
···
}

即如果判斷條件A為1,則執行B然後執行C然後中斷該代碼塊,如果判斷條件為2則執行C然後中斷該代碼塊,如果判斷條件為3則執行D然後中斷該代碼塊,等等。

循環

循環有3種形式:while,do-while和for。

while

形如

while(A){
    B
}
對判斷條件A進行判斷,當判斷條件為A時,多次執行B代碼。

do-while

形如

do{
    B
}while(A)

先執行一次B代碼,然後對判斷條件A進行判斷,當判斷條件A為真時,循環至花括號頭部執行B代碼。

for

形如

for(int i=0,j=0;i<100;i++,j++){
    B
}

首先對第一個;前面的部分執行,常見於循環控制對象進行初始化,多個對象可以使用,進行區分,然後對;間的循環控制條件進行判斷,如果條件為真則執行下方代碼塊,最後對;后的第三部分執行,比如對循環對象進行自增操作等。實例中就是先對i和j進行初始化為0,然後判斷i是否小於100,如果為真則執行B代碼,最後執行i自增和j自增,然後判斷i是否小於100,如果為真繼續循環,該代碼最後將執行B代碼段100次。
在編程時,循環常在數組等可迭代對象上進行。可迭代就是可以從前到后順序走一遍。對此,Java提供了一些便利的語法方便開發。形如

容器/數組 A;
for(int i:A){
    B
}

就是說對於容器或者數組A,迭代遍歷一遍,i會在每次循環中引用到A中的每個對象,然後執行B代碼塊。假如容器/數組A中有100個對象,那B就會被執行100次。

返回

return

結束該語句所在函數,返回返回值。
返回值類型由函數定義決定,如果不匹配且無法兼容則會IDE報錯。

Java的與眾不同

Java的流程控制中沒有goto語句,但是通過break和continue語句,實現了從深層循環中跳出的功能。通過在循環前標記label:,然後使用break labelcontinue label,即可跳出循環到指定位置或者是跳出本次循環到指定位置。

label1:
while(true){
    label2:
    while(true){
        continue label1;//結束本次循環,跳轉到外循環繼續循環
        break label1://結束外層循環
    }
}

感慨

Java的基本語法,與其他的語言有相似之處,也有不同之處,但編程本來就是一種實踐的技能,紙上得來終覺淺,絕知代碼要手擼,還是實打實的寫代碼才可以將知識轉化為能力。以及,文章真難寫。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

中國汽車工業三十強榜單發佈:冠軍上汽集團營收超萬億元

2016年5月26日,中國機械工業聯合會、中國汽車工業協會發佈最新中國汽車工業三十強榜單。上汽集團以2015年1.2萬億元的總營業收入繼續蟬聯冠軍,一汽和東風依然排第2、3位。

榜單中,蓋世汽車盤點出有乘用車公司16家,商用車公司4家,零部件公司7家,摩托車公司3家。本次榜單基於“年度匯總口徑快報”統計2015年營業收入,和上市公司合併財務報表中的營業收入存在計算上的差別,後者抵消了關聯交易部分。這也是為何上汽2015年年報中營收為670,448,223,139.34元,而本榜單中為1.2萬億元的緣故。

對比2015年排名(以2014年營業收入統計),19家企業排名沒有變化,比亞迪和威孚高科新上榜,陝西汽車和金城集團落榜。前十名中,僅長城(今年第9)和重汽(今年第10)互換了名次,其他8家企業都保持了去年的座次。提升最快的是中鼎,從第27提高到第24,上升3位。下降幅度最大的是法士特,從第24滑落至第28。

冠軍上汽集團去年營收總額超過第2名一汽和第3名東風總和。

以下是具體排名

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

大眾汽車計畫投資100億歐元新建一座電池工廠

據外媒報導,大眾汽車計畫投資100億歐元新建一座電池工廠,意欲轉型為一家領先的電動汽車製造商,從而使其可以自己掌控電動汽車的命運,而不是單純依賴于亞洲的電池廠商。

在大眾汽車6月22日召開年度股東大會以前,該公司的一個非執行理事會將對首席執行官馬蒂亞斯•穆勒(Matthias Müller)及其下屬一個團隊提出的計畫進行考慮。根據《商報》的報導,這項計畫的規模將需要足夠大到可以支援一個目標,即在未來十年時間裡將純電動汽車(PEV)的銷售量提高至100萬輛。

從投資額來看,大眾汽車計畫建設的這座工廠類似於特斯拉汽車的“超級工廠”(Gigafactry),而該公司所需電池的規模也意味著這將是一座龐大的工廠,但大眾汽車尚未透露更多細節。

據報導,大眾汽車的董事會很可能將會批准這項計畫,而身為該公司大股東之一的德國下薩克森州(Lower Saxony)也將支持該計畫。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

三、netcore跨平台之 Linux配置nginx負載均衡

前面兩章講了netcore在linux上部署以及配置nginx,並讓nginx代理webapi。

這一章主要講如何配置負載均衡,有些步驟在前兩章講的很詳細了,所以這一章我就不會一個個截圖了。

因為本人只有一個服務器。所以我會在同一台服務器上部署兩套差不多的實例。

同樣的代碼,我們在Program.cs進行了修改,如圖所示:

這裏我把原來的端口6666改成了8888

 

 然後你可以改一改你的接口部分的代碼,便於讓你更好的看到效果。

這裏把value1和value2改成value3和value4,這裡是為了看到測試效果,在實際的開發中這裏不用改。

 

 然後發布和上傳到服務器,如何發布和上傳,我在第一章有講到:https://www.cnblogs.com/dengbo/p/11878766.html

注意的是你同樣的地方新建一個新的目錄保存你新上傳的程序,netcore是我第一章建立的,netcore1是新建的,

你把你新的發布包放在netcore即可。如圖:

上傳結束后,在這個目錄中運行你的程序,輸入下面的命令

dotnet WebApiTest.dll   --server.urls "http://*:8888"

如圖所示

 

 然後去看看你的接口是否正常

 

 

好了,這裏的準備工作完成了,下面我們進入到nginx的配置的目錄中

輸入下面的命令:

cd /usr/local/nginx/conf

然後對文件進行編輯

vim nginx.conf

 

 我們需要在這裏修改一下配置。

在如圖的server的平級添加如下的代碼

upstream NgWebApi {
                server localhost:6666;
                server localhost:8888;
    }

上面的 NgWebApi是隨意寫的名稱,不要糾結這裏。

然後在修改 proxy_pass後面的內容:

proxy_pass http://NgWebApi;

最終的結果如下:

 

 這樣你就修改完成,輸入:wq退出並保存即可。

最後檢查並重啟nginx

/usr/local/nginx/sbin/nginx -t
/usr/local/nginx/sbin/nginx -s reload

最後不要忘記把你的8888端口的webapi啟動一下。

這裏我務必要提醒你,請進入到你的程序的目錄中執行這段代碼,

cd /root/netcore1
dotnet WebApiTest.dll   --server.urls "http://*:8888"

啟動如下:

 

 

 好了,配置結束了,下面我們來測試下

 

還是昨天的那個網站進行測試   https://www.sojson.com/httpRequest/

 

 

 

多次發送請求會出現下面的響應

 

 

看到上面兩個請求,就說明你配置成功了,是不是很簡單。

上面這種配置,系統會採用默認的輪詢訪問不同的端口,nginx作為強大的反向代理,強大的遠遠不止這裏

下面簡單講講分發策略。

1)、輪詢 ——輪流處理請求(這是系統默認的)

      每個請求按時間順序逐一分配到不同的應用服務器,如果應用服務器down掉,自動剔除它,剩下的繼續輪詢,如果您的服務器都差不多,建議這個。 

2)、權重 ——誰的設置的大,誰就承擔大部分的請求

      通過配置權重,指定輪詢幾率,權重和訪問比率成正比,用於應用服務器性能不均的情況,有時候你買的服務器可能參差不齊,有的性能強大

    有的一般,你可以通過設置權重,把服務器性能強大權重設置大一點,這樣可以合理分配壓力。 

3)ip_哈希算法

      每一次的請求按訪問iphash結果分配,這樣每個訪客固定訪問一個應用服務器,可以解決session共享的問題。

 

 

關於權重的策略,如下圖示的 你只要加一個  weight=6 即可這裏不一定是6,是整數都行。

 

 

 然後保存即可

這裏不要忘記重啟nginx,以及運行8888端口的程序了,如果你不會,可以看前面的部分

最後我們看看效果

結果和上面的測試結果差不多,唯一不同的是出現下面這個結果的次數要大於另外一個的。

 

 

到這裏就結束了,感謝觀看。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

SpringBoot 源碼解析 (六)—– Spring Boot的核心能力 – 內置Servlet容器源碼分析(Tomcat)

Spring Boot默認使用Tomcat作為嵌入式的Servlet容器,只要引入了spring-boot-start-web依賴,則默認是用Tomcat作為Servlet容器:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Servlet容器的使用

默認servlet容器

我們看看spring-boot-starter-web這個starter中有什麼

核心就是引入了tomcat和SpringMvc,我們先來看tomcat

Spring Boot默認支持Tomcat,Jetty,和Undertow作為底層容器。如圖:

而Spring Boot默認使用Tomcat,一旦引入spring-boot-starter-web模塊,就默認使用Tomcat容器。

切換servlet容器

那如果我么想切換其他Servlet容器呢,只需如下兩步:

  • 將tomcat依賴移除掉
  • 引入其他Servlet容器依賴

引入jetty:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <!--移除spring-boot-starter-web中的tomcat-->
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <!--引入jetty-->
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

Servlet容器自動配置原理

EmbeddedServletContainerAutoConfiguration

其中
EmbeddedServletContainerAutoConfiguration是嵌入式Servlet容器的自動配置類,該類在
spring-boot-autoconfigure.jar中的web模塊可以找到。

我們可以看到EmbeddedServletContainerAutoConfiguration被配置在spring.factories中,看過我前面文章的朋友應該知道SpringBoot自動配置的原理,這裏將EmbeddedServletContainerAutoConfiguration配置類加入到IOC容器中,接着我們來具體看看這個配置類:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication// 在Web環境下才會起作用
@Import(BeanPostProcessorsRegistrar.class)// 會Import一個內部類BeanPostProcessorsRegistrar
public class EmbeddedServletContainerAutoConfiguration {

    @Configuration
    // Tomcat類和Servlet類必須在classloader中存在 // 文章開頭我們已經導入了web的starter,其中包含tomcat和SpringMvc // 那麼classPath下會存在Tomcat.class和Servlet.class
    @ConditionalOnClass({ Servlet.class, Tomcat.class }) // 當前Spring容器中不存在EmbeddedServletContainerFactory類型的實例
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat {

        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            // 上述條件註解成立的話就會構造TomcatEmbeddedServletContainerFactory這個EmbeddedServletContainerFactory
            return new TomcatEmbeddedServletContainerFactory();
        }
    }
    
    @Configuration
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
            WebAppContext.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedJetty {

        @Bean
        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
            return new JettyEmbeddedServletContainerFactory();
        }

    }
    
    @Configuration
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedUndertow {

        @Bean
        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
            return new UndertowEmbeddedServletContainerFactory();
        }

    }
    
    //other code...
}

在這個自動配置類中配置了三個容器工廠的Bean,分別是:

  • TomcatEmbeddedServletContainerFactory

  • JettyEmbeddedServletContainerFactory

  • UndertowEmbeddedServletContainerFactory

這裏以大家熟悉的Tomcat為例,首先Spring Boot會判斷當前環境中是否引入了Servlet和Tomcat依賴,並且當前容器中沒有自定義的
EmbeddedServletContainerFactory的情況下,則創建Tomcat容器工廠。其他Servlet容器工廠也是同樣的道理。

EmbeddedServletContainerFactory

  • 嵌入式Servlet容器工廠
public interface EmbeddedServletContainerFactory {

    EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers);
}

內部只有一個方法,用於獲取嵌入式的Servlet容器。

該工廠接口主要有三個實現類,分別對應三種嵌入式Servlet容器的工廠類,如圖所示:

TomcatEmbeddedServletContainerFactory

以Tomcat容器工廠TomcatEmbeddedServletContainerFactory類為例:

public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
    
    //other code...
    
    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) {
        //創建一個Tomcat
        Tomcat tomcat = new Tomcat(); //配置Tomcat的基本環節
        File baseDir = (this.baseDirectory != null ? this.baseDirectory: createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
       tomcat.getService().addConnector(connector);
        customizeConnector(connector);
      tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        
        //包裝tomcat對象,返回一個嵌入式Tomcat容器,內部會啟動該tomcat容器
        return getTomcatEmbeddedServletContainer(tomcat);
    }
}

首先會創建一個Tomcat的對象,並設置一些屬性配置,最後調用getTomcatEmbeddedServletContainer(tomcat)方法,內部會啟動tomcat,我們來看看:

protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
    Tomcat tomcat) {
    return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}

該函數很簡單,就是來創建Tomcat容器並返回。看看TomcatEmbeddedServletContainer類:

public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {

    public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        
        //初始化嵌入式Tomcat容器,並啟動Tomcat
 initialize();
    }
    
    private void initialize() throws EmbeddedServletContainerException {
        TomcatEmbeddedServletContainer.logger
                .info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();
                try {
                    final Context context = findContext();
                    context.addLifecycleListener(new LifecycleListener() {

                        @Override
                        public void lifecycleEvent(LifecycleEvent event) {
                            if (context.equals(event.getSource())
                                    && Lifecycle.START_EVENT.equals(event.getType())) {
                                // Remove service connectors so that protocol
                                // binding doesn't happen when the service is
                                // started.
                                removeServiceConnectors();
                            }
                        }

                    });

                    // Start the server to trigger initialization listeners
                    //啟動tomcat
                    this.tomcat.start(); // We can re-throw failure exception directly in the main thread
                    rethrowDeferredStartupExceptions();

                    try {
                        ContextBindings.bindClassLoader(context, getNamingToken(context),
                                getClass().getClassLoader());
                    }
                    catch (NamingException ex) {
                        // Naming is not enabled. Continue
                    }

                    // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                    // blocking non-daemon to stop immediate shutdown
                    startDaemonAwaitThread();
                }
                catch (Exception ex) {
                    containerCounter.decrementAndGet();
                    throw ex;
                }
            }
            catch (Exception ex) {
                stopSilently();
                throw new EmbeddedServletContainerException(
                        "Unable to start embedded Tomcat", ex);
            }
        }
    }
}

到這裏就啟動了嵌入式的Servlet容器,其他容器類似。

Servlet容器啟動原理

SpringBoot啟動過程

我們回顧一下前面講解的SpringBoot啟動過程,也就是run方法:

public ConfigurableApplicationContext run(String... args) {
    // 計時工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    configureHeadlessProperty();

    // 第一步:獲取並啟動監聽器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 第二步:根據SpringApplicationRunListeners以及參數來準備環境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
        configureIgnoreBeanInfo(environment);

        // 準備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術字體
        Banner printedBanner = printBanner(environment);

        // 第三步:創建Spring容器
        context = createApplicationContext();

        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // 第四步:Spring容器前置處理
        prepareContext(context, environment, listeners, applicationArguments,printedBanner);

        // 第五步:刷新容器
 refreshContext(context);

     // 第六步:Spring容器後置處理
        afterRefresh(context, applicationArguments);

      // 第七步:發出結束執行的事件
        listeners.started(context);
        // 第八步:執行Runners
        this.callRunners(context, applicationArguments);
        stopWatch.stop();
        // 返回容器
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, exceptionReporters, ex);
        throw new IllegalStateException(ex);
    }
}

我們回顧一下第三步:創建Spring容器

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            //根據應用環境,創建不同的IOC容器
            contextClass = Class.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

創建IOC容器,如果是web應用,則創建
AnnotationConfigEmbeddedWebApplicationContext的IOC容器;如果不是,則創建AnnotationConfigApplicationContext的IOC容器;很明顯我們創建的容器是AnnotationConfigEmbeddedWebApplicationContext
接着我們來看看
第五步,刷新容器
refreshContext(context);

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
}

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    //調用容器的refresh()方法刷新容器
 ((AbstractApplicationContext) applicationContext).refresh();
}

容器刷新過程

調用抽象父類AbstractApplicationContext的refresh()方法;

AbstractApplicationContext

 1 public void refresh() throws BeansException, IllegalStateException {
 2     synchronized (this.startupShutdownMonitor) {
 3         /**
 4          * 刷新上下文環境
 5          */
 6         prepareRefresh();
 7 
 8         /**
 9          * 初始化BeanFactory,解析XML,相當於之前的XmlBeanFactory的操作,
10          */
11         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
12 
13         /**
14          * 為上下文準備BeanFactory,即對BeanFactory的各種功能進行填充,如常用的註解@Autowired @Qualifier等
15          * 添加ApplicationContextAwareProcessor處理器
16          * 在依賴注入忽略實現*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
17          * 註冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的實例注入進去
18          */
19         prepareBeanFactory(beanFactory);
20 
21         try {
22             /**
23              * 提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
24              */
25             postProcessBeanFactory(beanFactory);
26 
27             /**
28              * 激活各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
29              * 執行對應的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
30              */
31             invokeBeanFactoryPostProcessors(beanFactory);
32 
33             /**
34              * 註冊攔截Bean創建的Bean處理器,即註冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意兩者的區別
35              * 注意,這裏僅僅是註冊,並不會執行對應的方法,將在bean的實例化時執行對應的方法
36              */
37             registerBeanPostProcessors(beanFactory);
38 
39             /**
40              * 初始化上下文中的資源文件,如國際化文件的處理等
41              */
42             initMessageSource();
43 
44             /**
45              * 初始化上下文事件廣播器,並放入applicatioEventMulticaster,如ApplicationEventPublisher
46              */
47             initApplicationEventMulticaster();
48 
49             /**
50  * 給子類擴展初始化其他Bean 51              */
52  onRefresh(); 53 
54             /**
55              * 在所有bean中查找listener bean,然後註冊到廣播器中
56              */
57             registerListeners();
58 
59             /**
60              * 設置轉換器
61              * 註冊一個默認的屬性值解析器
62              * 凍結所有的bean定義,說明註冊的bean定義將不能被修改或進一步的處理
63              * 初始化剩餘的非惰性的bean,即初始化非延遲加載的bean
64              */
65             finishBeanFactoryInitialization(beanFactory);
66 
67             /**
68              * 通過spring的事件發布機制發布ContextRefreshedEvent事件,以保證對應的監聽器做進一步的處理
69              * 即對那種在spring啟動后需要處理的一些類,這些類實現了ApplicationListener<ContextRefreshedEvent>,
70              * 這裏就是要觸發這些類的執行(執行onApplicationEvent方法)
71              * spring的內置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
72              * 完成初始化,通知生命周期處理器lifeCycleProcessor刷新過程,同時發出ContextRefreshEvent通知其他人
73              */
74             finishRefresh();
75         }
76 
77         finally {
78     
79             resetCommonCaches();
80         }
81     }
82 }

我們看第52行的方法:

protected void onRefresh() throws BeansException {

}

很明顯抽象父類AbstractApplicationContext中的onRefresh是一個空方法,並且使用protected修飾,也就是其子類可以重寫onRefresh方法,那我們看看其子類AnnotationConfigEmbeddedWebApplicationContext中的onRefresh方法是如何重寫的,AnnotationConfigEmbeddedWebApplicationContext又繼承EmbeddedWebApplicationContext,如下:

public class AnnotationConfigEmbeddedWebApplicationContext extends EmbeddedWebApplicationContext {

那我們看看其父類EmbeddedWebApplicationContext 是如何重寫onRefresh方法的:

EmbeddedWebApplicationContext

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        //核心方法:會獲取嵌入式的Servlet容器工廠,並通過工廠來獲取Servlet容器
 createEmbeddedServletContainer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start embedded container", ex);
    }
}

在createEmbeddedServletContainer方法中會獲取嵌入式的Servlet容器工廠,並通過工廠來獲取Servlet容器:

 1 private void createEmbeddedServletContainer() {
 2     EmbeddedServletContainer localContainer = this.embeddedServletContainer;
 3     ServletContext localServletContext = getServletContext();
 4     if (localContainer == null && localServletContext == null) {
 5         //先獲取嵌入式Servlet容器工廠
 6         EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();  7         //根據容器工廠來獲取對應的嵌入式Servlet容器
 8         this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());  9     }
10     else if (localServletContext != null) {
11         try {
12             getSelfInitializer().onStartup(localServletContext);
13         }
14         catch (ServletException ex) {
15             throw new ApplicationContextException("Cannot initialize servlet context",ex);
16         }
17     }
18     initPropertySources();
19 }

關鍵代碼在第6和第8行,先獲取Servlet容器工廠,然後根據容器工廠來獲取對應的嵌入式Servlet容器

獲取Servlet容器工廠

protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
    //從Spring的IOC容器中獲取EmbeddedServletContainerFactory.class類型的Bean
    String[] beanNames = getBeanFactory().getBeanNamesForType(EmbeddedServletContainerFactory.class); //調用getBean實例化EmbeddedServletContainerFactory.class
    return getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class);
}

我們看到先從Spring的IOC容器中獲取EmbeddedServletContainerFactory.class類型的Bean,然後調用getBean實例化EmbeddedServletContainerFactory.class,大家還記得我們第一節Servlet容器自動配置類EmbeddedServletContainerAutoConfiguration中注入Spring容器的對象是什麼嗎?當我們引入spring-boot-starter-web這個啟動器后,會注入TomcatEmbeddedServletContainerFactory這個對象到Spring容器中,所以這裏獲取到的Servlet容器工廠是TomcatEmbeddedServletContainerFactory,然後調用

TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer方法獲取Servlet容器,並且啟動Tomcat,大家可以看看文章開頭的getEmbeddedServletContainer方法。

大家看一下第8行代碼獲取Servlet容器方法的參數getSelfInitializer(),這是個啥?我們點進去看看

private ServletContextInitializer getSelfInitializer() {
    //創建一個ServletContextInitializer對象,並重寫onStartup方法,很明顯是一個回調方法
    return new ServletContextInitializer() { public void onStartup(ServletContext servletContext) throws ServletException { EmbeddedWebApplicationContext.this.selfInitialize(servletContext); } };
}

創建一個ServletContextInitializer對象,並重寫onStartup方法,很明顯是一個回調方法,這裏給大家留一點疑問:

  • ServletContextInitializer對象創建過程是怎樣的?
  • onStartup是何時調用的?
  • onStartup方法的作用是什麼?

ServletContextInitializer是 Servlet 容器初始化的時候,提供的初始化接口。這裏涉及到Servlet、Filter實例的註冊,我們留在下一篇具體講

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

詳解JavaScript錯誤捕獲和上報流程

 

 

 

 

怎麼捕獲錯誤並且處理,是一門語言必備的知識。在JavaScript中也是如此。

那怎麼捕獲錯誤呢?初看好像很簡單,try-catch就可以了嘛!但是有的時候我們發現情況卻繁多複雜。

  • Q1: 同步可以try-catch,但一個異步回調,比如setTimeOut里的函數還可以try-catch嗎?

  • Q2: Promise的錯誤捕獲怎麼做?

  • Q3: async/await怎麼捕獲錯誤?

  • Q4: 我能夠在全局環境下捕獲錯誤並且處理嗎?

  • Q5: React16有什麼新的錯誤捕獲方式嗎?

  • Q6: 捕獲之後怎麼上報和處理?

 

問題有點多,我們一個一個來。

 

Q1. 同步代碼里的錯誤捕獲方式

在同步代碼里,我們是最簡單的,只要try-catch就完了 

function test1 () {
  try {
    throw Error ('callback err');
  } catch (error) {
    console.log ('test1:catch err successfully');
  }
}
test1();

輸出結果如下,顯然是正常的

Q2. 普通的異步回調里的錯誤捕獲方式(Promise時代以前)

上面的問題來了,我們還能通過直接的try-catch在異步回調外部捕獲錯誤嗎?我們試一試 

// 嘗試在異步回調外部捕獲錯誤的結果
function test2 () {
  try {
    setTimeout (function () {
      throw Error ('callback err');
    });
  } catch (error) {
    console.log ('test2:catch err successfully');
  }
}
test2(); 

輸出

注意這裏的Uncaught Error的文本,它告訴我們錯誤沒有被成功捕捉。

為什麼呢? 因為try-catch的是屬於同步代碼,它執行的時候,setTimeOut內部的的匿名函數還沒有執行呢。而內部的那個匿名函數執行的時候,try-catch早就執行完了。( error的內心想法:哈哈,只要我跑的夠慢,try-catch還是追不上我!)

但是我們簡單想一想,誒我們把try-catch寫到函數裏面不就完事了嘛!

 

 

function test2_1 () {
  setTimeout (function () {
    try {
      throw Error ('callback err');
    } catch (error) {
      console.log ('test2_1:catch err successfully');
    }
  });
}
test2_1();

輸出結果如下,告訴我們這方法可行

 

總結下Promise時代以前,異步回調中捕獲和處理錯誤的方法

  • 在異步回調內部編寫try-catch去捕獲和處理,不要在外部哦

  • 很多異步操作會開放error事件,我們根據事件去操作就可以了

Q3. Promise里的錯誤捕獲方式

可通過Promise.catch方法捕獲

function test3 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  }).catch (err => {
    console.log ('promise error');
  });
}

輸出結果

>> reject方法調用和throw Error都可以通過Promise.catch方法捕獲

function test4 () {
  new Promise ((resolve, reject) => {
    reject ('promise reject error');
  }).catch (err => {
    console.log (err);
  });
} 

輸出結果

 

>> then方法中的失敗回調和Promise.catch的關係

  • 如果前面的then方法沒寫失敗回調,失敗時後面的catch是會被調用的

  • 如果前面的then方法寫了失敗回調,又沒拋出,那麼後面的catch就不會被調用了

// then方法沒寫失敗回調
function test5 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  })
    .then (success => {})
    .catch (err => {
      console.log ('the error has not been swallowed up');
    });
}
// then方法寫了失敗回調
function test5 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  })
    .then (success => {},err => {})
    .catch (err => {
      console.log ('the error has not been swallowed up');
    });
}

輸出分別為

1.the error has not been swallowed up
2.無輸出

Q4.async/await里的錯誤捕獲方式

對於async/await這種類型的異步,我們可以通過try-catch去解決

async function test6 () {
  try {
    await getErrorP ();
  } catch (error) {
    console.log ('async/await error with throw error');
  }
}
 
function getErrorP () {
  return new Promise ((resolve, reject) => {
    throw Error ('promise error');
  });
}
test6();

輸出結果如下

 

>> 如果被await修飾的Promise因為reject調用而變化,它也是能被try-catch的

(我已經證明了這一點,但是這裏位置不夠,我寫不下了)

Q5.在全局環境下如何監聽錯誤

window.onerror可以監聽全局錯誤,但是很顯然錯誤還是會拋出

window.onerror = function (err) {
  console.log ('global error');
};
throw Error ('global error');

 

輸出如下

 

Q6.在React16以上如何監聽錯誤

>> componentDidCatch和getDerivedStateFromError鈎子函數

class Bar extends React.Component {
  // 監聽組件錯誤
  componentDidCatch(error, info) {
    this.setState({ error, info });
  }
  // 更新 state 使下一次渲染能夠显示降級后的 UI
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  render() {
  }
}

 

 

有錯誤,那肯定要上報啊!不上報就發現不了Bug這個樣子。Sentry這位老哥就是個人才,日誌記錄又好看,每次見面就像回家一樣

 

 

Sentry簡單介紹

Sentry provides open-source and hosted error monitoring that helps all software
teams discover, triage, and prioritize errors in real-time.
One million developers at over fifty thousand companies already ship
better software faster with Sentry. Won’t you join them?
—— Sentry官網

 

Sentry是一個日誌上報系統,Sentry 是一個實時的日誌記錄和匯總處理的平台。專註於錯誤監控,發現和數據處理,可以讓我們不再依賴於用戶反饋才能發現和解決線上bug。讓我們簡單看一下Sentry支持哪些語言和平台吧

 

在JavaScript領域,Sentry的支持也可以說是面面俱到

 

參考鏈接
https://docs.sentry.io/platforms/ 

Sentry的功能簡單說就是,你在代碼中catch錯誤,然後調用Sentry的方法,然後Sentry就會自動幫你分析和整理錯誤日誌,例如下面這張圖截取自Sentry的網站中

 

在JavaScript中使用Sentry 

1.首先呢,你當然要註冊Sentry的賬號

這個時候Sentry會自動給你分配一個唯一標示,這個標示在Sentry里叫做 dsn

2. 安卓模塊並使用基礎功能

安裝@sentry/browser 

npm install @sentry/browser

 

在項目中初始化並使用

import * as Sentry from '@sentry/browser';
 
Sentry.init ({
  dsn: 'xxxx',
});
 
try {
  throw Error ('我是一個error');
} catch (err) {
    // 捕捉錯誤
  Sentry.captureException (err);
}

3.上傳sourceMap以方便在線上平台閱讀出錯的源碼

 

// 安裝
$ npm install --save-dev @sentry/webpack-plugin
$ yarn add --dev @sentry/webpack-plugin
 
// 配置webpack
const SentryWebpackPlugin = require('@sentry/webpack-plugin');
module.exports = {
  // other configuration
  plugins: [
    new SentryWebpackPlugin({
      include: '.',
      ignoreFile: '.sentrycliignore',
      ignore: ['node_modules', 'webpack.config.js'],
      configFile: 'sentry.properties'
    })
  ]
}; 

4. 為什麼不是raven.js?

 

// 已經廢棄,雖然你還是可以用
var Raven = require('raven-js');
Raven
  .config('xxxxxxxxxxx_dsn')
  .install();

 

Sentry的核心功能總結

捕獲錯誤

try { aFunctionThatMightFail(); } catch (err) { Sentry.captureException(err); }

 

設置該錯誤發生的用戶信息

下面每個選項都是可選的,但必須 存在一個選項 才能使Sentry SDK捕獲用戶: id 

Sentry.setUser({
    id:"penghuwan12314"
  email: "penghuwan@example.com",
  username:"penghuwan",
  ip_addressZ:'xxx.xxx.xxx.xxx'
  });

 

設置額外數據

Sentry.setExtra("character_name", "Mighty Fighter");
設置作用域 
Sentry.withScope(function(scope) {
    // 下面的set的效果只存在於函數的作用域內
  scope.setFingerprint(['Database Connection Error']);
  scope.setUser(someUser);
  Sentry.captureException(err);
});
// 在這裏,上面的setUser的設置效果會消失

 

設置錯誤的分組

整理日誌信息,避免過度冗餘 

Sentry.configureScope(function(scope) {
  scope.setFingerprint(['my-view-function']);
});

 

設置錯誤的級別

在閱讀日誌時可以確定各個bug的緊急度,確定排查的優先書序

Sentry.captureMessage('this is a debug message', 'debug');
//fatal,error,warning,info,debug五個值
// fatal最嚴重,debug最輕

 

自動記錄某些事件

例如下面的方法,會在每次屏幕調整時完成上報 

window.addEventListener('resize', function(event){
  Sentry.addBreadcrumb({
    category: 'ui',
    message: 'New window size:' + window.innerWidth + 'x' + window.innerHeight,
    level: 'info'
  });
})

Sentry實踐的運用

根據環境設置不同的dsn

let dsn;
  if (env === 'test') {
    dsn = '測試環境的dsn';
  } else {
    dsn =
      '正式環境的dsn';
  }
 
Sentry.init ({
  dsn
});

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

在開發框架中擴展微軟企業庫,支持使用ODP.NET(Oracle.ManagedDataAccess.dll)訪問Oracle數據庫,基於Enterprise Library的Winform開發框架實現支持國產達夢數據庫的擴展操作

在前面隨筆《》中介紹了在代碼生成工具中使用ODP.NET(Oracle.ManagedDataAccess.dll)訪問Oracle數據庫,如果我們在框架應用中需要使用這個如何處理了?由於我們開發框架底層主要使用微軟企業庫(目前用的版本是4.1),如果是使用它官方的Oracle擴展,那麼就是使用EntLibContrib.Data.OdpNet(這個企業庫擴展類庫使用了Oracle.DataAccess.dll),不過這種方式還是受限於32位和64位的問題;假如我們使用ODP.NET(Oracle.ManagedDataAccess.dll)方式,可以使用自己擴展企業庫支持即可,類似於我們支持國產數據庫–達夢數據庫一樣的原理,使用Oracle.ManagedDataAccess類庫可以避免32位和64位衝突問題,實現統一兼容。

1、擴展支持ODP.NET(Oracle.ManagedDataAccess.dll)訪問

為了實現自定義的擴展支持,我們需要對企業庫的擴展類庫進行處理,類似我們之前編寫達夢數據庫的自定義擴展類庫一樣,這方面可以了解下之前的隨筆《》,我們現在增加對ODP.NET(Oracle.ManagedDataAccess.dll)方式的擴展支持。

首先我們創建一個項目,並通過Nugget的方式獲得對應的Oracle.ManagedDataAccess.dll類庫,參考企業庫對於Mysql的擴展或者其他的擴展,稍作調整即可。

 OracleDatabase類似下面代碼

using System;
using System.Data;
using System.Data.Common;

using Microsoft.Practices.EnterpriseLibrary.Common;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.EnterpriseLibrary.Data.Configuration;
using Oracle.ManagedDataAccess.Client;

namespace EntLibContrib.Data.OracleManaged
{
    /// <summary>
    /// <para>Oracle數據庫對象(使用ODP驅動)</para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// Internally uses OracleProvider from Oracle to connect to the database.
    /// </para>
    /// </remarks>
    [DatabaseAssembler(typeof(OracleDatabaseAssembler))]
    public class OracleDatabase : Database
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="OracleDatabase"/> class
        /// with a connection string.
        /// </summary>
        /// <param name="connectionString">The connection string.</param>
        public OracleDatabase(string connectionString) : base(connectionString, OracleClientFactory.Instance)
        {
        }
        
        /// <summary>
        /// <para>
        /// Gets the parameter token used to delimit parameters for the
        /// Oracle database.</para>
        /// </summary>
        /// <value>
        /// <para>The '?' symbol.</para>
        /// </value>
        protected char ParameterToken
        {
            get
            {
                return ':';
            }
        }

        .........

主要就是把對應的類型修改為Oracle的即可,如Oracle的名稱,以及參數的符號為 :等地方,其他的一一調整即可,不在贅述。

完成后,修改程序集名稱,編譯為 EntLibContrib.Data.OracleManaged.dll 即可。

 

2、框架應用的數據庫配置項設置

完成上面的步驟,我們就可以在配置文件中增加配置信息如下所示,它就能正常的解析並處理了。

 

 上面使用了兩種方式,一種是官方擴展的EntLibContrib.Data.OdpNet方式,一種是我們這裏剛剛出爐的 EntLibContrib.Data.OracleManaged方式,完整的數據庫支持文件信息如下所示。

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data"/>
    <section name="oracleConnectionSettings" type="EntLibContrib.Data.OdpNet.Configuration.OracleConnectionSettings, EntLibContrib.Data.OdpNet" />
  </configSections>
  <connectionStrings>
    <!--SQLServer數據庫的連接字符串-->
    <add name="sqlserver" providerName="System.Data.SqlClient" connectionString="Persist Security Info=False;Data Source=(local);Initial Catalog=WinFramework;Integrated Security=SSPI"/>
    
    <!--Oracle數據庫的連接字符串-->
    <add name="oracle" providerName="System.Data.OracleClient" connectionString="Data Source=orcl;User ID=whc;Password=whc"/>
    
    <!--MySQL數據庫的連接字符串-->
    <add name="mysql" providerName="MySql.Data.MySqlClient" connectionString="Server=localhost;Database=WinFramework;Uid=root;Pwd=123456;"/>
    
    <!--PostgreSQL數據庫的連接字符串-->
    <add name="npgsql" providerName="Npgsql" connectionString="Server=localhost;Port=5432;Database=postgres;User Id=postgres;Password=123456"/>
    
    <!--路徑符號|DataDirectory|代表當前運行目錄-->    
    <!--SQLite數據庫的連接字符串-->
    <add name="sqlite"  providerName="System.Data.SQLite" connectionString="Data Source=|DataDirectory|\WinFramework.db;Version=3;" />
    <!--Microsoft Access數據庫的連接字符串-->
    <add name="access" providerName="System.Data.OleDb" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\WinFramework.mdb;User ID=Admin;Jet OLEDB:Database Password=;" />   
    
    <!--IBM DB2數據庫的連接字符串-->
    <add    name="db2" providerName="IBM.Data.DB2"    connectionString="database=whc;uid=whc;pwd=123456"/>
    
    <!--採用OdpNet方式的Oracle數據庫的連接字符串-->
    <add    name="oracle2"    providerName="Oracle.DataAccess.Client"    connectionString="Data Source=orcl;User id=win;Password=win;" />
    <add    name="oracle3"    providerName="OracleManaged"    connectionString="Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl.mshome.net)));User ID=win;Password=win" />
  </connectionStrings>
  <dataConfiguration defaultDatabase="oracle3">
    <providerMappings>
      <add databaseType="EntLibContrib.Data.MySql.MySqlDatabase, EntLibContrib.Data.MySql" name="MySql.Data.MySqlClient" />
      <add databaseType="EntLibContrib.Data.SQLite.SQLiteDatabase, EntLibContrib.Data.SqLite" name="System.Data.SQLite" />
      <add databaseType="EntLibContrib.Data.PostgreSql.NpgsqlDatabase, EntLibContrib.Data.PostgreSql" name="Npgsql" />      
      <add databaseType="EntLibContrib.Data.DB2.DB2Database, EntLibContrib.Data.DB2" name="IBM.Data.DB2" />
      <add databaseType="EntLibContrib.Data.OdpNet.OracleDatabase, EntLibContrib.Data.OdpNet" name="Oracle.DataAccess.Client" />
      <add databaseType="EntLibContrib.Data.Dm.DmDatabase, EntLibContrib.Data.Dm" name="Dm" />
      <!--增加ODP.NET(Oracle.ManagedDataAccess.dll)方式的擴展支持-->
      <add databaseType="EntLibContrib.Data.OracleManaged.OracleDatabase, EntLibContrib.Data.OracleManaged" name="OracleManaged" />
    </providerMappings>
  </dataConfiguration>
  
  <appSettings>

  </appSettings>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
    <supportedRuntime version="v2.0.50727"/>
  </startup>
</configuration>

這樣我們底層就可以實現多種數據庫的兼容訪問了。

採用不同的數據庫,我們需要為不同數據庫的訪問層進行生成處理,如為SQLServer數據的表生成相關的數據訪問層DALSQL,裏面放置各個表對象的內容,不過由於採用了相關的繼承類處理和基於數據庫的代碼生成,需要調整的代碼很少。

我們來編寫一段簡單的程序代碼來測試支持這種ODP.net方式,測試代碼如下所示。

private void btnGetData_Click(object sender, EventArgs e)
{
    string sql = "select * from T_Customer";// + " Where Name = :name";
    Database db = DatabaseFactory.CreateDatabase();
    DbCommand command = db.GetSqlStringCommand(sql);
    //command.Parameters.Add(new OracleParameter("name", "張三"));

    using (var ds = db.ExecuteDataSet(command))
    {
        this.dataGridView1.DataSource = ds.Tables[0];   
    }
}

測試界面效果如下所示。

以上這些處理,可以適用於Web框架、Bootstrap開發框架、Winform開發框架、混合式開發框架中的應用,也就是CS、BS都可以使用。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

中國發改委協調汽車充電設施建設 加快政策落實

從中國發改委網站獲悉,停車場充電設施建設協調會7月18日下午召開。會議表示將針對大城市、特大城市存在的停車位資源緊張、社會停車場投資主體多、充電設施企業盈利模式相對單一等問題,進一步推進相關工作。  
  停車場充電設施建設協調會由中國國家發改委基礎產業司副司長鄭劍主持,就2016年第二批城市停車場項目配建充電基礎設施問題,與安徽、江蘇、江西、陝西、浙江、湖北、上海、大連、廈門等地方發展改革委、充電基礎設施服務企業和國家電網公司進行交流座談。   據國家能源局電力司初步統計,截至今年6月底,中國全國已建成公共充電樁8.1萬個,比去年底增長65%;隨車建成私人充電樁超過5萬個,比去年底增長約12%。1-6月全國新能源汽車充電量超過6億kWh,替代燃油約20萬噸,電動汽車的發展對能源結構調整和城市環境提升貢獻明顯。   為新能源汽車的推廣和應用創造良好的環境,國家能源局相關部門加快了推動充電樁政策規劃的落實,組織起草加快居民區充電基礎設施建設的檔。該文件有望7月份出臺,將有效推進居民區和工作場所建樁工作,合理優化公共充電樁佈局,提高公共充電樁利用率。   文章來源:中國發展網

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

基於.NetStandard的簡易EventBus實現-基礎實現

一、問題背景

  最近離職來到了一家新的公司,原先是在乙方工作,這回到了甲方,在這一個月中,發現目前的業務很大一部分是靠輪詢實現的,例如:通過輪詢判斷數據處於B狀態了,則輪詢到數據后執行某種動作,這個其實是非常浪費的,並且對於數據的實時性也會不怎麼友好,基於以上的情況,在某天開車堵車時候,想到了之前偶然了解過的事件總線(EventBus),對比了公司當前的場景后,覺得事件總線應該是可以滿足需求的(PS:只是我覺得這個有問題,很多人不覺得有問題),那既然想到了,那就想自己是否可以做個事件總線的輪子

二、什麼是事件總線

  我們知道事件是由一個Publisher跟一個或多個的Subsriber組成,但是在實際的使用過程中,我們會發現,Subsriber必須知道Publisher是誰才可以註冊事件,進而達到目的,那這其實就是一種耦合,為了解決這個問題,就出現了事件總線的模式,事件總線允許不同的模塊之間進行彼此通信而又不需要相互依賴,如下圖所示,通過EventBus,讓Publisher以及Subsriber都只需要對事件源(EventData)進行關注,不用管Publisher是誰,那麼EventBus主要是做了一些什麼事呢?

三、EventBus做了什麼事?

  1、EventBus實現了對於事件的註冊以及取消註冊的管理

  2、EventBus內部維護了一份事件源與事件處理程序的對應關係,並且通過這個對應關係在事件發布的時候可以找到對應的處理程序去執行

  3、EventBus應該要支持默認就註冊事件源與處理程序的關係,而不需要開發人員手動去註冊(這裏可以讓開發人員去控制自動還是手動)

四、具體實現思路

   首先在事件總線中,存在註冊、取消註冊以及觸發事件這三種行為,所以我們可以將這三種行為抽象一個接口出來,最終的接口代碼如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace MEventBus.Core
{
    public interface IEventBus
    {
        #region 接口註冊
        void Register<TEventData>(Type handlerType) where TEventData : IEventData;
        void Register(Type eventType, Type handlerType);
        void Register(string eventType, Type handlerType);
        #endregion

        #region 接口取消註冊
        void Unregister<TEventData>(Type handler) where TEventData : IEventData;
        void Unregister(Type eventType, Type handlerType);
        void Unregister(string eventType, Type handlerType);
        #endregion


        void Trigger(string pubKey, IEventData eventData);
        Task TriggerAsync(string pubKey, IEventData eventData);
        Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData;
        void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData;
    }
}

  在以上代碼中發現有些方法是有IEventData約束的,這邊IEventData就是約束入參行為,原則上規定,每次觸發的EventData都需要繼承IEventData,而註冊的行為也是直接跟入參類型相關,具體代碼如下:

using System;
using System.Collections.Generic;
using System.Text;

namespace MEventBus.Core
{
    public interface IEventData
    {
        string Id { get; set; }
        DateTime EventTime { get; set; }
        object EventSource { get; set; }
    }
}

  接下來我們看下具體的實現代碼

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace MEventBus.Core
{
    public class EventBus : IEventBus
    {
        private static ConcurrentDictionary<string, List<Type>> dicEvent = new ConcurrentDictionary<string, List<Type>>();
        private IResolve _iresolve { get; set; }
        public EventBus(IResolve resolve)
        {
            _iresolve = resolve;
            InitRegister();
        }

        public void InitRegister()
        {
            if (dicEvent.Count > 0)
            {
                return;
            }
            //_iresolve = ioc_container;
            dicEvent = new ConcurrentDictionary<string, List<Type>>();
            //自動掃描類型並且註冊
            foreach (var file in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"))
            {
                var ass = Assembly.LoadFrom(file);
                foreach (var item in ass.GetTypes().Where(p => p.GetInterfaces().Contains(typeof(IEventHandler))))
                {
                    if (item.IsClass)
                    {
                        foreach (var item1 in item.GetInterfaces())
                        {
                            foreach (var item2 in item1.GetGenericArguments())
                            {
                                if (item2.GetInterfaces().Contains(typeof(IEventData)))
                                {
                                    Register(item2, item);
                                }
                            }
                        }
                    }
                }
            }
        }
        //註冊以及取消註冊的時候需要加鎖處理
        private static readonly object obj = new object();

        #region 註冊事件
        public void Register<TEventData>(Type handlerType) where TEventData : IEventData
        {
            //將數據存儲到mapDic
            var dataType = typeof(TEventData).FullName;
            Register(dataType, handlerType);
        }
        public void Register(Type eventType, Type handlerType)
        {
            var dataType = eventType.FullName;
            Register(dataType, handlerType);
        }
        public void Register(string pubKey, Type handlerType)
        {
            lock (obj)
            {
                //將數據存儲到dicEvent
                if (dicEvent.Keys.Contains(pubKey) == false)
                {
                    dicEvent[pubKey] = new List<Type>();
                }
                if (dicEvent[pubKey].Exists(p => p.GetType() == handlerType) == false)
                {
                    //IEventHandler obj = Activator.CreateInstance(handlerType) as IEventHandler;
                    dicEvent[pubKey].Add(handlerType);
                }
            }
        }



        #endregion

        #region 取消事件註冊
        public void Unregister<TEventData>(Type handler) where TEventData : IEventData
        {
            var dataType = typeof(TEventData);
            Unregister(dataType, handler);
        }

        public void Unregister(Type eventType, Type handlerType)
        {
            string _key = eventType.FullName;
            Unregister(_key, handlerType);
        }
        public void Unregister(string eventType, Type handlerType)
        {
            lock (obj)
            {
                if (dicEvent.Keys.Contains(eventType))
                {
                    if (dicEvent[eventType].Exists(p => p.GetType() == handlerType))
                    {
                        dicEvent[eventType].Remove(dicEvent[eventType].Find(p => p.GetType() == handlerType));
                    }
                }
            }
        }
        #endregion

        #region Trigger觸發
        //trigger時候需要記錄到數據庫
        public void Trigger<TEventData>(TEventData eventData) where TEventData : IEventData
        {
            var dataType = eventData.GetType().FullName;
            //獲取當前的EventData綁定的所有Handler
            Notify(dataType, eventData);
        }

        public void Trigger(string pubKey, IEventData eventData)
        {
            //獲取當前的EventData綁定的所有Handler
            Notify(pubKey, eventData);
        }
        public async Task TriggerAsync<TEventData>(TEventData eventData) where TEventData : IEventData
        {
            await Task.Factory.StartNew(new Action(()=> 
            {
                var dataType = eventData.GetType().FullName;
                Notify(dataType, eventData);
            }));
        }
        public async Task TriggerAsync(string pubKey, IEventData eventData)
        {
            await Task.Factory.StartNew(new Action(() =>
            {
                var dataType = eventData.GetType().FullName;
                Notify(pubKey, eventData);
            }));
        }
        //通知每成功執行一個就需要記錄到數據庫
        private void Notify<TEventData>(string eventType, TEventData eventData) where TEventData : IEventData
        {
            //獲取當前的EventData綁定的所有Handler
            var handlerTypes = dicEvent[eventType];
            foreach (var handlerType in handlerTypes)
            {
                var resolveObj = _iresolve.Resolve(handlerType);
                IEventHandler<TEventData> handler = resolveObj as IEventHandler<TEventData>;
                handler.Handle(eventData);

            }
        }
        #endregion
    }
}

  代碼說明:

  1、如上的EventBus是繼承了IEventBus后的具體實現,小夥伴可能看到在構造函數里,有一個接口參數IResolve,這個主要是為了將解析的過程進行解耦,由於在一些WebApi的項目中,更加多的是使用IOC的機制進行對象的創建,那基於IResolve就可以實現不同的對象創建方式(內置的是通過反射實現)

  2、InitRegister方法通過遍歷當前目錄下的dll文件,去尋找所有實現了IEventHandler<IEventData>接口的信息,並且自動註冊到EventBus中,所以在實際使用過程中,應該是沒有機會去適用register註冊的

  3、觸發機制實現了同步以及異步的調用,這個從方法命名中就可以看出來

五、程序Demo

  TestHandler2(繼承IEventHandler)

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using MEventBus.Core;

namespace MEventBusHandler.Test
{
    public class TestHandler2 : IEventHandler<TestEventData>
    {
        public void Handle(TestEventData eventData)
        {
            Thread.Sleep(2000);
            MessageBox.Show(eventData.EventTime.ToString());
        }
    }
}

  TestEventData(繼承EventData,EventData是繼承了IEventData的代碼)

using MEventBus.Core;
using System;
using System.Collections.Generic;
using System.Text;

namespace MEventBusHandler.Test
{
    public class TestEventData : EventData
    { }
}

  調用代碼

using MEventBus.Core;
using MEventBusHandler.Test;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MEventBus.Test
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            TestHandler.OnOut += TestHandler_OnOut;
        }

        private void TestHandler_OnOut(object sender, EventArgs e)
        {
            MessageBox.Show("Hello World");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var task = new MEventBus.Core.EventBus(new ReflectResolve()).TriggerAsync(new TestEventData());
            task.ContinueWith((obj) => {
                MessageBox.Show("事情全部做完");
            });
        }

        private void button2_Click(object sender, EventArgs e)
        {
           new EventBus(new ReflectResolve()).Trigger(new TestEventData());
        }
    }


}

  執行結果

 

 

 

 

 我在真正的Demo中,其實是註冊了2個handler,可以在後續公布的項目地址里看到

六、總結

  從有這個想法開始,到最終實現這個事件總線,大概總共花了2,3天的時間(PS:晚上回家獨自默默幹活),目前只能說是有一個初步可以使用的版本,並且還存在着一些問題:

  1、在.NetFrameWork下(目前公司還不想升級到.NetCore,吐血。。),如果使用AutoFac創建EventBus(單例模式下),如果Handler也使用AutoFac進行創建,會出現要麼對象創建失敗,要麼handler里的對象與調用方的對象不是同一個實例,為了解決這個問題,我讓EventBus不再是單例模式,將dicEvent變成了靜態,暫時表面解決

  2、未考慮跨進程的實現(感覺用savorboard大佬的就可以了)

  3、目前這個東西在一個小的新項目里使用,暫時在測試環境還算沒啥問題,各位小夥伴如果有類似需求,可以做個參考

  由於個人原因,在測試上可能會有所不夠,如果有什麼bug的話,還請站內信告知,感謝(ps:文字表達弱雞,技術渣渣,各位多多包涵)

  最後:附上項目地址:

 

 

作者: Mango

出處: 

關於自己:專註.Net桌面開發以及Web後台開發,對.NetCore、微服務、DevOps,K8S等感興趣,最近到了個甲方公司準備休養一段時間

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,如有問題, 可站內信告知.

 

 

 

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

[springboot 開發單體web shop] 7. 多種形式提供商品列表

上文回顧

我們實現了仿jd的輪播廣告以及商品分類的功能,並且講解了不同的注入方式,本節我們將繼續實現我們的電商主業務,商品信息的展示。

需求分析

首先,在我們開始本節編碼之前,我們先來分析一下都有哪些地方會對商品進行展示,打開jd首頁,鼠標下拉可以看到如下:

可以看到,在大類型下查詢了部分商品在首頁進行展示(可以是最新的,也可以是網站推薦等等),然後點擊任何一個分類,可以看到如下:

我們一般進到電商網站之後,最常用的一個功能就是搜索, 結果如下:

選擇任意一個商品點擊,都可以進入到詳情頁面,這個是單個商品的信息展示。
綜上,我們可以知道,要實現一個電商平台的商品展示,最基本的包含:

  • 首頁推薦/最新上架商品
  • 分類查詢商品
  • 關鍵詞搜索商品
  • 商品詳情展示

接下來,我們就可以開始商品相關的業務開發了。

首頁商品列表|IndexProductList

開發梳理

我們首先來實現在首頁展示的推薦商品列表,來看一下都需要展示哪些信息,以及如何進行展示。

  • 商品主鍵(product_id)
  • 展示圖片(image_url)
  • 商品名稱(product_name)
  • 商品價格(product_price)
  • 分類說明(description)
  • 分類名稱(category_name)
  • 分類主鍵(category_id)
  • 其他…

編碼實現

根據一級分類查詢

遵循開發順序,自下而上,如果基礎mapper解決不了,那麼優先編寫SQL mapper,因為我們需要在同一張表中根據parent_id遞歸的實現數據查詢,當然我們這裏使用的是錶鏈接的方式實現。因此,common mapper無法滿足我們的需求,需要自定義mapper實現。

Custom Mapper實現

和根據一級分類查詢子分類一樣,在項目mscx-shop-mapper中添加一個自定義實現接口com.liferunner.custom.ProductCustomMapper,然後在resources\mapper\custom路徑下同步創建xml文件mapper/custom/ProductCustomMapper.xml,此時,因為我們在上節中已經配置了當前文件夾可以被容器掃描到,所以我們添加的新的mapper就會在啟動時被掃描加載,代碼如下:

/**
 * ProductCustomMapper for : 自定義商品Mapper
 */
public interface ProductCustomMapper {

    /***
     * 根據一級分類查詢商品
     *
     * @param paramMap 傳遞一級分類(map傳遞多參數)
     * @return java.util.List<com.liferunner.dto.IndexProductDTO>
     */
    List<IndexProductDTO> getIndexProductDtoList(@Param("paramMap") Map<String, Integer> paramMap);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liferunner.custom.ProductCustomMapper">
    <resultMap id="IndexProductDTO" type="com.liferunner.dto.IndexProductDTO">
        <id column="rootCategoryId" property="rootCategoryId"/>
        <result column="rootCategoryName" property="rootCategoryName"/>
        <result column="slogan" property="slogan"/>
        <result column="categoryImage" property="categoryImage"/>
        <result column="bgColor" property="bgColor"/>
        <collection property="productItemList" ofType="com.liferunner.dto.IndexProductItemDTO">
            <id column="productId" property="productId"/>
            <result column="productName" property="productName"/>
            <result column="productMainImageUrl" property="productMainImageUrl"/>
            <result column="productCreateTime" property="productCreateTime"/>
        </collection>
    </resultMap>
    <select id="getIndexProductDtoList" resultMap="IndexProductDTO" parameterType="Map">
        SELECT
        c.id as rootCategoryId,
        c.name as rootCategoryName,
        c.slogan as slogan,
        c.category_image as categoryImage,
        c.bg_color as bgColor,
        p.id as productId,
        p.product_name as productName,
        pi.url as productMainImageUrl,
        p.created_time as productCreateTime
        FROM category c
        LEFT JOIN products p
        ON c.id = p.root_category_id
        LEFT JOIN products_img pi
        ON p.id = pi.product_id
        WHERE c.type = 1
        AND p.root_category_id = #{paramMap.rootCategoryId}
        AND pi.is_main = 1
        LIMIT 0,10;
    </select>
</mapper>

Service實現

serviceproject 創建com.liferunner.service.IProductService接口以及其實現類com.liferunner.service.impl.ProductServiceImpl,添加查詢方法如下:

public interface IProductService {

    /**
     * 根據一級分類id獲取首頁推薦的商品list
     *
     * @param rootCategoryId 一級分類id
     * @return 商品list
     */
    List<IndexProductDTO> getIndexProductDtoList(Integer rootCategoryId);
    ...
}

---
    
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ProductServiceImpl implements IProductService {

    // RequiredArgsConstructor 構造器注入
    private final ProductCustomMapper productCustomMapper;

    @Transactional(propagation = Propagation.SUPPORTS)
    @Override
    public List<IndexProductDTO> getIndexProductDtoList(Integer rootCategoryId) {
        log.info("====== ProductServiceImpl#getIndexProductDtoList(rootCategoryId) : {}=======", rootCategoryId);
        Map<String, Integer> map = new HashMap<>();
        map.put("rootCategoryId", rootCategoryId);
        val indexProductDtoList = this.productCustomMapper.getIndexProductDtoList(map);
        if (CollectionUtils.isEmpty(indexProductDtoList)) {
            log.warn("ProductServiceImpl#getIndexProductDtoList未查詢到任何商品信息");
        }
        log.info("查詢結果:{}", indexProductDtoList);
        return indexProductDtoList;
    }
}

Controller實現

接着,在com.liferunner.api.controller.IndexController中實現對外暴露的查詢接口:

@RestController
@RequestMapping("/index")
@Api(value = "首頁信息controller", tags = "首頁信息接口API")
@Slf4j
public class IndexController {
    ...
    @Autowired
    private IProductService productService;

    @GetMapping("/rootCategorys")
    @ApiOperation(value = "查詢一級分類", notes = "查詢一級分類")
    public JsonResponse findAllRootCategorys() {
        log.info("============查詢一級分類==============");
        val categoryResponseDTOS = this.categoryService.getAllRootCategorys();
        if (CollectionUtils.isEmpty(categoryResponseDTOS)) {
            log.info("============未查詢到任何分類==============");
            return JsonResponse.ok(Collections.EMPTY_LIST);
        }
        log.info("============一級分類查詢result:{}==============", categoryResponseDTOS);
        return JsonResponse.ok(categoryResponseDTOS);
    }
    ...
}

Test API

編寫完成之後,我們需要對我們的代碼進行測試驗證,還是通過使用RestService插件來實現,當然,大家也可以通過Postman來測試,結果如下:

商品列表|ProductList

如開文之初我們看到的京東商品列表一樣,我們先分析一下在商品列表頁面都需要哪些元素信息?

開發梳理

商品列表的展示按照我們之前的分析,總共分為2大類:

  • 選擇商品分類之後,展示當前分類下所有商品
  • 輸入搜索關鍵詞后,展示當前搜索到相關的所有商品

在這兩類中展示的商品列表數據,除了數據來源不同以外,其他元素基本都保持一致,那麼我們是否可以使用統一的接口來根據參數實現隔離呢? 理論上不存在問題,完全可以通過傳參判斷的方式進行數據回傳,但是,在我們實現一些可預見的功能需求時,一定要給自己的開發預留後路,也就是我們常說的可拓展性,基於此,我們會分開實現各自的接口,以便於後期的擴展。
接着來分析在列表頁中我們需要展示的元素,首先因為需要分上述兩種情況,因此我們需要在我們API設計的時候分別處理,針對於
1.分類的商品列表展示,需要傳入的參數有:

  • 分類id
  • 排序(在電商列表我們常見的幾種排序(銷量,價格等等))
  • 分頁相關(因為我們不可能把數據庫中所有的商品都取出來)
    • PageNumber(當前第幾頁)
    • PageSize(每頁显示多少條數據)

2.關鍵詞查詢商品列表,需要傳入的參數有:

  • 關鍵詞
  • 排序(在電商列表我們常見的幾種排序(銷量,價格等等))
  • 分頁相關(因為我們不可能把數據庫中所有的商品都取出來)
    • PageNumber(當前第幾頁)
    • PageSize(每頁显示多少條數據)

需要在頁面展示的信息有:

  • 商品id(用於跳轉商品詳情使用)
  • 商品名稱
  • 商品價格
  • 商品銷量
  • 商品圖片
  • 商品優惠

編碼實現

根據上面我們的分析,接下來開始我們的編碼:

根據商品分類查詢

根據我們的分析,肯定不會在一張表中把所有數據獲取全,因此我們需要進行多表聯查,故我們需要在自定義mapper中實現我們的功能查詢.

ResponseDTO 實現

根據我們前面分析的前端需要展示的信息,我們來定義一個用於展示這些信息的對象com.liferunner.dto.SearchProductDTO,代碼如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SearchProductDTO {
    private String productId;
    private String productName;
    private Integer sellCounts;
    private String imgUrl;
    private Integer priceDiscount;
    //商品優惠,我們直接計算之後返回優惠后價格
}

Custom Mapper 實現

com.liferunner.custom.ProductCustomMapper.java中新增一個方法接口:

    List<SearchProductDTO> searchProductListByCategoryId(@Param("paramMap") Map<String, Object> paramMap);

同時,在mapper/custom/ProductCustomMapper.xml中實現我們的查詢方法:

<select id="searchProductListByCategoryId" resultType="com.liferunner.dto.SearchProductDTO" parameterType="Map">
        SELECT
        p.id as productId,
        p.product_name as productName,
        p.sell_counts as sellCounts,
        pi.url as imgUrl,
        tp.priceDiscount
        FROM products p
        LEFT JOIN products_img pi
        ON p.id = pi.product_id
        LEFT JOIN
        (
        SELECT product_id, MIN(price_discount) as priceDiscount
        FROM products_spec
        GROUP BY product_id
        ) tp
        ON tp.product_id = p.id
        WHERE pi.is_main = 1
        AND p.category_id = #{paramMap.categoryId}
        ORDER BY
        <choose>
            <when test="paramMap.sortby != null and paramMap.sortby == 'sell'">
                p.sell_counts DESC
            </when>
            <when test="paramMap.sortby != null and paramMap.sortby == 'price'">
                tp.priceDiscount ASC
            </when>
            <otherwise>
                p.created_time DESC
            </otherwise>
        </choose>
    </select>

主要來說明一下這裏的<choose>模塊,以及為什麼不使用if標籤。
在有的時候,我們並不希望所有的條件都同時生效,而只是想從多個選項中選擇一個,但是在使用IF標籤時,只要test中的表達式為 true,就會執行IF 標籤中的條件。MyBatis 提供了 choose 元素。IF標籤是與(and)的關係,而 choose 是或(or)的關係。
它的選擇是按照順序自上而下,一旦有任何一個滿足條件,則選擇退出。

Service 實現

然後在servicecom.liferunner.service.IProductService中添加方法接口:

    /**
     * 根據商品分類查詢商品列表
     *
     * @param categoryId 分類id
     * @param sortby     排序方式
     * @param pageNumber 當前頁碼
     * @param pageSize   每頁展示多少條數據
     * @return 通用分頁結果視圖
     */
    CommonPagedResult searchProductList(Integer categoryId, String sortby, Integer pageNumber, Integer pageSize);

在實現類com.liferunner.service.impl.ProductServiceImpl中,實現上述方法:

    // 方法重載
    @Override
    public CommonPagedResult searchProductList(Integer categoryId, String sortby, Integer pageNumber, Integer pageSize) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("categoryId", categoryId);
        paramMap.put("sortby", sortby);
        // mybatis-pagehelper
        PageHelper.startPage(pageNumber, pageSize);
        val searchProductDTOS = this.productCustomMapper.searchProductListByCategoryId(paramMap);
        // 獲取mybatis插件中獲取到信息
        PageInfo<?> pageInfo = new PageInfo<>(searchProductDTOS);
        // 封裝為返回到前端分頁組件可識別的視圖
        val commonPagedResult = CommonPagedResult.builder()
                .pageNumber(pageNumber)
                .rows(searchProductDTOS)
                .totalPage(pageInfo.getPages())
                .records(pageInfo.getTotal())
                .build();
        return commonPagedResult;
    }

在這裏,我們使用到了一個mybatis-pagehelper插件,會在下面的福利講解中分解。

Controller 實現

繼續在com.liferunner.api.controller.ProductController中添加對外暴露的接口API:

@GetMapping("/searchByCategoryId")
    @ApiOperation(value = "查詢商品信息列表", notes = "根據商品分類查詢商品列表")
    public JsonResponse searchProductListByCategoryId(
        @ApiParam(name = "categoryId", value = "商品分類id", required = true, example = "0")
        @RequestParam Integer categoryId,
        @ApiParam(name = "sortby", value = "排序方式", required = false)
        @RequestParam String sortby,
        @ApiParam(name = "pageNumber", value = "當前頁碼", required = false, example = "1")
        @RequestParam Integer pageNumber,
        @ApiParam(name = "pageSize", value = "每頁展示記錄數", required = false, example = "10")
        @RequestParam Integer pageSize
    ) {
        if (null == categoryId || categoryId == 0) {
            return JsonResponse.errorMsg("分類id錯誤!");
        }
        if (null == pageNumber || 0 == pageNumber) {
            pageNumber = DEFAULT_PAGE_NUMBER;
        }
        if (null == pageSize || 0 == pageSize) {
            pageSize = DEFAULT_PAGE_SIZE;
        }
        log.info("============根據分類:{} 搜索列表==============", categoryId);

        val searchResult = this.productService.searchProductList(categoryId, sortby, pageNumber, pageSize);
        return JsonResponse.ok(searchResult);
    }

因為我們的請求中,只會要求商品分類id是必填項,其餘的調用方都可以不提供,但是如果不提供的話,我們系統就需要給定一些默認的參數來保證我們的系統正常穩定的運行,因此,我定義了com.liferunner.api.controller.BaseController,用於存儲一些公共的配置信息。

/**
 * BaseController for : controller 基類
 */
@Controller
public class BaseController {
    /**
     * 默認展示第1頁
     */
    public final Integer DEFAULT_PAGE_NUMBER = 1;
    /**
     * 默認每頁展示10條數據
     */
    public final Integer DEFAULT_PAGE_SIZE = 10;
}

Test API

測試的參數分別是:categoryId : 51 ,sortby : price,pageNumber : 1,pageSize : 5

可以看到,我們查詢到7條數據,總頁數totalPage為2,並且根據價格從小到大進行了排序,證明我們的編碼是正確的。接下來,通過相同的代碼邏輯,我們繼續實現根據搜索關鍵詞進行查詢。

根據關鍵詞查詢

Response DTO 實現

使用上面實現的com.liferunner.dto.SearchProductDTO.

Custom Mapper 實現

com.liferunner.custom.ProductCustomMapper中新增方法:

List<SearchProductDTO> searchProductList(@Param("paramMap") Map<String, Object> paramMap);

mapper/custom/ProductCustomMapper.xml中添加查詢SQL:

<select id="searchProductList" resultType="com.liferunner.dto.SearchProductDTO" parameterType="Map">
        SELECT
        p.id as productId,
        p.product_name as productName,
        p.sell_counts as sellCounts,
        pi.url as imgUrl,
        tp.priceDiscount
        FROM products p
        LEFT JOIN products_img pi
        ON p.id = pi.product_id
        LEFT JOIN
        (
        SELECT product_id, MIN(price_discount) as priceDiscount
        FROM products_spec
        GROUP BY product_id
        ) tp
        ON tp.product_id = p.id
        WHERE pi.is_main = 1
        <if test="paramMap.keyword != null and paramMap.keyword != ''">
            AND p.item_name LIKE "%${paramMap.keyword}%"
        </if>
        ORDER BY
        <choose>
            <when test="paramMap.sortby != null and paramMap.sortby == 'sell'">
                p.sell_counts DESC
            </when>
            <when test="paramMap.sortby != null and paramMap.sortby == 'price'">
                tp.priceDiscount ASC
            </when>
            <otherwise>
                p.created_time DESC
            </otherwise>
        </choose>
    </select>

Service 實現

com.liferunner.service.IProductService中新增查詢接口:

    /**
     * 查詢商品列表
     *
     * @param keyword    查詢關鍵詞
     * @param sortby     排序方式
     * @param pageNumber 當前頁碼
     * @param pageSize   每頁展示多少條數據
     * @return 通用分頁結果視圖
     */
    CommonPagedResult searchProductList(String keyword, String sortby, Integer pageNumber, Integer pageSize);

com.liferunner.service.impl.ProductServiceImpl實現上述接口方法:

    @Override
    public CommonPagedResult searchProductList(String keyword, String sortby, Integer pageNumber, Integer pageSize) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("keyword", keyword);
        paramMap.put("sortby", sortby);
        // mybatis-pagehelper
        PageHelper.startPage(pageNumber, pageSize);
        val searchProductDTOS = this.productCustomMapper.searchProductList(paramMap);
        // 獲取mybatis插件中獲取到信息
        PageInfo<?> pageInfo = new PageInfo<>(searchProductDTOS);
        // 封裝為返回到前端分頁組件可識別的視圖
        val commonPagedResult = CommonPagedResult.builder()
                .pageNumber(pageNumber)
                .rows(searchProductDTOS)
                .totalPage(pageInfo.getPages())
                .records(pageInfo.getTotal())
                .build();
        return commonPagedResult;
    }

上述方法和之前searchProductList(Integer categoryId, String sortby, Integer pageNumber, Integer pageSize)唯一的區別就是它是肯定搜索關鍵詞來進行數據查詢,使用重載的目的是為了我們後續不同類型的業務擴展而考慮的。

Controller 實現

com.liferunner.api.controller.ProductController中添加關鍵詞搜索API:

    @GetMapping("/search")
    @ApiOperation(value = "查詢商品信息列表", notes = "查詢商品信息列表")
    public JsonResponse searchProductList(
        @ApiParam(name = "keyword", value = "搜索關鍵詞", required = true)
        @RequestParam String keyword,
        @ApiParam(name = "sortby", value = "排序方式", required = false)
        @RequestParam String sortby,
        @ApiParam(name = "pageNumber", value = "當前頁碼", required = false, example = "1")
        @RequestParam Integer pageNumber,
        @ApiParam(name = "pageSize", value = "每頁展示記錄數", required = false, example = "10")
        @RequestParam Integer pageSize
    ) {
        if (StringUtils.isBlank(keyword)) {
            return JsonResponse.errorMsg("搜索關鍵詞不能為空!");
        }
        if (null == pageNumber || 0 == pageNumber) {
            pageNumber = DEFAULT_PAGE_NUMBER;
        }
        if (null == pageSize || 0 == pageSize) {
            pageSize = DEFAULT_PAGE_SIZE;
        }
        log.info("============根據關鍵詞:{} 搜索列表==============", keyword);

        val searchResult = this.productService.searchProductList(keyword, sortby, pageNumber, pageSize);
        return JsonResponse.ok(searchResult);
    }

Test API

測試參數:keyword : 西鳳,sortby : sell,pageNumber : 1,pageSize : 10

根據銷量排序正常,查詢關鍵詞正常,總條數32,每頁10條,總共3頁正常。

福利講解

在本節編碼實現中,我們使用到了一個通用的mybatis分頁插件mybatis-pagehelper,接下來,我們來了解一下這個插件的基本情況。

mybatis-pagehelper

如果各位小夥伴使用過:, 那麼對於這個就很容易理解了,它其實就是基於來實現的,當攔截到原始SQL之後,對SQL進行一次改造處理。
我們來看看我們自己代碼中的實現,根據springboot編碼三部曲:

1.添加依賴

        <!-- 引入mybatis-pagehelper 插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>

有同學就要問了,為什麼引入的這個依賴和我原來使用的不同?以前使用的是:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>

答案就在這裏:

我們使用的是springboot進行的項目開發,既然使用的是springboot,那我們完全可以用到它的自動裝配特性,作者幫我們實現了這麼一個,我們只需要參考示例來編寫就ok了。

2.改配置

# mybatis 分頁組件配置
pagehelper:
  helperDialect: mysql #插件支持12種數據庫,選擇類型
  supportMethodsArguments: true

3.改代碼

如下示例代碼:

    @Override
    public CommonPagedResult searchProductList(String keyword, String sortby, Integer pageNumber, Integer pageSize) {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("keyword", keyword);
        paramMap.put("sortby", sortby);
        // mybatis-pagehelper
        PageHelper.startPage(pageNumber, pageSize);
        val searchProductDTOS = this.productCustomMapper.searchProductList(paramMap);
        // 獲取mybatis插件中獲取到信息
        PageInfo<?> pageInfo = new PageInfo<>(searchProductDTOS);
        // 封裝為返回到前端分頁組件可識別的視圖
        val commonPagedResult = CommonPagedResult.builder()
                .pageNumber(pageNumber)
                .rows(searchProductDTOS)
                .totalPage(pageInfo.getPages())
                .records(pageInfo.getTotal())
                .build();
        return commonPagedResult;
    }

在我們查詢數據庫之前,我們引入了一句PageHelper.startPage(pageNumber, pageSize);,告訴mybatis我們要對查詢進行分頁處理,這個時候插件會啟動一個攔截器com.github.pagehelper.PageInterceptor,針對所有的query進行攔截,添加自定義參數和添加查詢數據總數。(後續我們會打印sql來證明。)

當查詢到結果之後,我們需要將我們查詢到的結果通知給插件,也就是PageInfo<?> pageInfo = new PageInfo<>(searchProductDTOS);com.github.pagehelper.PageInfo是對插件針對分頁做的一個屬性包裝,具體可以查看)。

至此,我們的插件使用就已經結束了。但是為什麼我們在後面又封裝了一個對象來對外進行返回,而不是使用查詢到的PageInfo呢?這是因為我們實際開發過程中,為了數據結構的一致性做的一次結構封裝,你也可不實現該步驟,都是對結果沒有任何影響的。

SQL打印對比

2019-11-21 12:04:21 INFO  ProductController:134 - ============根據關鍵詞:西鳳 搜索列表==============
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4ff449ba] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1980420239 wrapping com.mysql.cj.jdbc.ConnectionImpl@563b22b1] will not be managed by Spring
==>  Preparing: SELECT count(0) FROM products p LEFT JOIN products_img pi ON p.id = pi.product_id LEFT JOIN (SELECT product_id, MIN(price_discount) AS priceDiscount FROM products_spec GROUP BY product_id) tp ON tp.product_id = p.id WHERE pi.is_main = 1 AND p.product_name LIKE "%西鳳%" 
==> Parameters: 
<==    Columns: count(0)
<==        Row: 32
<==      Total: 1
==>  Preparing: SELECT p.id as productId, p.product_name as productName, p.sell_counts as sellCounts, pi.url as imgUrl, tp.priceDiscount FROM product p LEFT JOIN products_img pi ON p.id = pi.product_id LEFT JOIN ( SELECT product_id, MIN(price_discount) as priceDiscount FROM products_spec GROUP BY product_id ) tp ON tp.product_id = p.id WHERE pi.is_main = 1 AND p.product_name LIKE "%西鳳%" ORDER BY p.sell_counts DESC LIMIT ? 
==> Parameters: 10(Integer)

我們可以看到,我們的SQL中多了一個SELECT count(0),第二條SQL多了一個LIMIT參數,在代碼中,我們很明確的知道,我們並沒有显示的去搜索總數和查詢條數,可以確定它就是插件幫我們實現的。

源碼下載

下節預告

下一節我們將繼續開發商品詳情展示以及商品評價業務,在過程中使用到的任何開發組件,我都會通過專門的一節來進行介紹的,兄弟們末慌!

gogogo!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!