環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

整個內飾最吸睛的地方是方向盤,兩輻式設計的方向盤尺寸不算小,但是會感覺有點奇怪,喇叭蓋板是從下往上包,而不是一般的從上往下,而且整個設計形狀有點彆扭,需要一點時間去適應,具介紹稱整個形狀調轉一百八十度就是跟中控面板的形狀一致的,好像是這樣,但是打滿一百八十度的感覺會更怪,這算是個彩蛋。
說起海馬這個品牌,是需要花一點時間去回憶起來,然後感覺到有點惋惜,近幾年自主品牌突飛猛進,很多都發展到令人佩服的地步,眾多車型充斥着各大主流市場,而海馬則沒能成為其中之一,對於第一代的福美來記憶猶新,年銷量可達10萬輛,而現在已經不堪回首。
海馬從1988建立至今已經30年了,福美來再推新車型,那麼在而立之年,這款新車有着怎樣的表現呢?它對比自身有什麼突破,對比對手競爭力又在哪裡呢?前往海南三亞,一探究竟。
外觀:這大概是最好看的福美來了
這裏需要提一下福美來家族的其他車型,雖然臉和顏值這個是比較主觀的東西,但客觀地講,福美來家族的外觀設計比起同級競品,是存在一定差距的,譬如福美來MpV和福美來F7,兩款車其實是兄弟車型,只是兩款車型採用了不同的設計。
福美來MpV的設計是比較年輕、張揚一點,外觀最奪目的就是前臉的進氣格柵和大燈的設計,大量的豎立鍍鉻飾條,對於顏值高低保留意見,而F7採用了橫向的鍍鉻飾條,看起來更居家一點,而大燈的造型是一樣的,相比福美來MpV,我更容易接受福美來F7。
福美來F5定位緊湊型家轎,車身尺寸和福美來大體一致,可以看做是兄弟車型,長寬高分別是4698*1805*1477(mm),軸距2685mm,只是寬度差了1mm。
尺寸相同,但是前臉比起福美來,就如福美來F7之於福美來MpV,採用差異化的設計風格,更居家的設計讓更容易接受一些,最大的變化在於前進氣格柵,相比起福美來那粗大的橫飾條,F5採用了滿天星前臉格柵,據介紹星星的形狀是用上下翻轉重合的兩個海馬車標合成的這麼一個設計理念,一眼看上去的話是比較有辨識度。
大燈的形狀其實是跟福美來一致的,試駕的是高配車型,全系採用鹵素光源,有加透鏡。
LED日行燈手動擋和自動擋的頂配才有配備,布置在最外側,霧燈靠裡邊。
對比起福美來也是有一點區別的,福美來沒有LED日行燈,兩側進氣口的裝飾都不一樣,比起它比較犀利的兩個尖角裝飾,F5的這個設計明顯更大氣,有橫向拉伸感。
個人覺得側面是F5最大的看點,上腰線斷開若隱若現,下腰線一直延伸至尾部,非常有動感,特別是寶藍色的車色,特別騷,配合三亞藍天、藍海、藍車,完美。
換一個角度看着車身側面的腰線,簡直賞心悅目。
高配車型配備無鑰匙進入和無鑰匙啟動,這個價位的車型來說,應該是值得肯定的表現。
電動調節后視鏡為全系標配,不過後視鏡加熱/自動摺疊這些就沒有了。
前後都採用盤剎制動,標配205 /55 R16的輪胎,輪胎是韓泰的,輪轂形狀也是比較運動年輕的設計。
車尾也有可圈可點之處,其實可以看到尾部是想奔馳C級的那樣翹起一個小尾巴,營造出不錯的動感。
個人覺得尾燈的設計比前大燈更具協調感,雙邊共雙出的排氣是裝飾的,視覺效果還想;試駕車型是頂配車型,配備后駐車雷達和倒車影像,除了最低配以外,其餘車型均為標配項,比較良心。
內飾設計有點“皮”&配置基本夠用
內飾和福美來相比完全是兩個設計風格,但是有幾個地方讓人覺得設計師有點“調皮”,整體的設計風格比較居家簡約,把空調按鍵布置在空調下方,是跟奔馳相似的做法,中控用料基本都是硬塑料,相信這個價位是無功無過吧。
整個內飾最吸睛的地方是方向盤,兩輻式設計的方向盤尺寸不算小,但是會感覺有點奇怪,喇叭蓋板是從下往上包,而不是一般的從上往下,而且整個設計形狀有點彆扭,需要一點時間去適應,具介紹稱整個形狀調轉一百八十度就是跟中控面板的形狀一致的,好像是這樣,但是打滿一百八十度的感覺會更怪,這算是個彩蛋?設計師很皮。
方向盤用的是液壓助力,尺寸比較大,但是形狀不粗,反而比較細,握感不飽滿,8點、4點鐘方向其實是設計有手指凹紋的,方便這樣操作
只有一側有功能按鍵,右側是空的,這樣給人感覺欠妥,或許日後會有功能加上?
另外是一鍵啟動的位置,把它布置在空調按鍵下方,說不上市奇特,但是不常見,另外更不常見的是居然有配備無線充電,不過是頂配車型才有。
儀錶盤是中規中矩,雙圓設計比較常見,然後加入了石英錶設計元素。
配備8英寸中控屏,Carplay/CarLife手機互聯、藍牙連接,都是除最低配以外其餘車型配備,高配車型還有車載導航。
ESp和电子手剎也是除最低配以外其餘車型配備,另外,這裏的後備廂開啟鍵目前找到的唯一一個打開方式,似乎找不到其他方式打開後備廂,這多少會有點不便,比如買完東西準備放後備廂,只能先打開車門然後再打開後備廂最後把東西拿到後備廂放進去。
空間方面,體驗者身高為1.75米,在調整好駕駛坐姿后,在後排有接近2拳腿部空間,頭部空間一拳由於,表現還是不錯的。
有點不足的是作為一輛前驅車,中央地板的凸起高度比較高,而且只有兩個頭枕,這樣的話後排坐3個人中間那個就會有點難受。
後備廂官方容積為415L,不算大,但縱深比較深,有點不足是第二排座椅不能放到,中間也不能把扶手拉下來從後備廂拿東西,這個有待改進,另外再提一下,後備廂真的應該加上一個打開按鈕或者拉扣。
好開舒服,底盤基本功到位是最大的優勢
F5搭載和福美來一樣的1.6L發動機,最大馬力125匹,在6000轉才能爆發十成功力,匹配5擋手動和6擋手自一體變速箱,發動機採用全鋁發動機,最大扭矩151N·m,峰值扭矩從四千轉后開始輸出,作為一款自吸發動機來說,賬面數據也就這樣了。
在習慣了渦輪浪潮下,開着這裏僅有125匹馬力的轎車真的感覺到不少差距,在市區或者說中低速路段行駛其實還算夠用,超個車提提速什麼的都沒什麼大問題,但是上了高速的話真的顯得有點佛系了,即使地板油猛催它提速依然是比較慢的,讓一度以為是不是忘記鬆手剎?不對呀,电子的。
還需要挑刺的是深踩油門發動機提速起來噪音抑制的一般般,轟鳴聲也會傳到車內,不過在6AT變速箱兢兢業業的工作下,時刻都會抓住油門的動作,只要有較大的動力需求就會立馬提速,總的來說平順性已經響應性都是不用擔心的,只不過提速真的急不來,畢竟一輛家轎,滿足平順的駕駛體驗以及夠用的動力就已經很高分了。
接下來都是F5值得肯定的點,首先是油門和剎車,這是首先感覺到的有點,兩者調教的很線性,而且很均勻,不會像日系或者其他車型前半段或者前三分之一要麼一下子用力過猛要麼就基本沒反應,特別是剎車,做得很線性,加上前後盤剎,踩得越深是越有勁,感覺怎麼樣都能剎得住,這給日常駕駛帶來了足夠信心。
而且這個油門配上自吸天生的特性,真的是想要多少踩多少,不像渦輪發動機那樣猛的沖一下讓人覺得措手不及,就這兩個方面就能給比較好的影像。
前懸架是麥弗遜獨立懸架,后懸架是雙E型多連桿獨立懸挂,后懸都是獨立的再這個級別當中可以說是相當厚道了,很多十來萬的都是板車呢,所以F5開起來路過一些顛簸路面,它可以處理得游刃有餘,完全沒有廉價感,濾震是做的很到位,是一款稱職的家用車。
另外底盤的紮實感以及對轉彎、變道的側傾做的是比較不錯的,整個底盤很整、很穩,跑了半天高速深有體會,時速接近120km/h,整個車身一點都不飄,甚至有點大眾那樣的感覺,而且在一些中高速的變道或者是超車,就完全不像一款幾萬塊的車的表現,應該像十來萬或者以上,側傾小,車身姿態平穩,底盤基本功沒丟,有祖傳的功力。
總的來說,F5最大的優勢在於底盤的質感以及整個偏向紮實的調教,但是不足依然很多,小缺點依然是存在一部分的,另外最大的爭議點或許是外形設計和內飾,這個跟同級競品相比確實有待提高,海馬或許是不想隨波逐流,保留自己的特色,但是覺得,有很多東西是可以借鑒一下的,即便順應大部分消費者的審美口味也可以做出自己的特色,最重要的好事賣得好。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!

有誰想到這款車當年竟然是名棄嬰,四處找人收留。Jimny的型號的原本來自一間叫希望汽車的生產廠家,該公司研製了一部希望之星ON360,但鑒於公司規模,老闆小野定良計劃向三菱汽車推銷代工制權,但三菱礙於希望汽車的往績,並沒有接受這單生意,最後只能硬着頭皮自行生產,並於1967年底上市,可惜銷量不如預期。
每部車都有着自己的型號名,一般來說就是字母加数字的組合,或單純以文字作為型號名,在當中有的更隱藏別具意義的故事,今天就和大家分享幾個有趣的故事。
淑女的由來
“惡魔Z”、“大魔王”這些都是我們對Fairlady的尊稱,日產經典跑車系列Fairlady這個名字的靈感來源於百老匯歌舞劇My Fair Lady, 1961年時任社長川又克二正在美國公幹,機緣巧合下觀看了這套長壽歌舞劇,同期美國即將推出新款跑車SLp213,一下子就決定新車冠以Fairlady之名,希望它像該歌劇一樣受歡迎,並成為經典。
380萬中挑一
Sunny(陽光)於1966年推出,曾經是日產主力家庭用車,當年日產在報紙廣告上預告推出一升的新車,並進行募集車名活動,在緊接一輪廣告攻勢逐步發布新車細節后,接連一個月宣傳吸引了848萬人參與,收到共380萬個名字推薦,最後選定了切合車型概念的Sunny為名。
南美洲“劈蕉佬”
不少越野發燒友及工程人員鍾情的三菱pajero(帕傑羅),因其英文發音和粵語里的“劈蕉佬”有着99%的相似度,瞬間顯得親民又剛強。至於pajero名稱的起源,則是取自於生活在阿根廷南部高原地帶的一種山貓,它活躍於崎嶇山間,正好吻合pajero的強勁野外走破性能,更讓這隻猛獸衝出南美洲,穿梭於全球各地山野。
人棄我取,終成經典
硬派越野車一直給人一種高大上的感覺,但也有例外,鈴木Jimny(吉姆尼)就是這樣一款小巧見稱的越野車,即使到了今天仍然是不少越野愛好者的寵兒。有誰想到這款車當年竟然是名棄嬰,四處找人收留。Jimny的型號的原本來自一間叫希望汽車的生產廠家,該公司研製了一部希望之星ON360,但鑒於公司規模,老闆小野定良計劃向三菱汽車推銷代工制權,但三菱礙於希望汽車的往績,並沒有接受這單生意,最後只能硬着頭皮自行生產,並於1967年底上市,可惜銷量不如預期。於是小野定良改去向鈴木汽車負責人鈴木修推銷此車,鈴木修對此車一見鍾情,即使公司上下都反對仍堅持一己之見,最終以1,200萬日元向希望汽車買斷整個設計,並於1970年4月推出初代Jimny,造就這部鈴木的招牌車款。
買回來的名字
在Integra未面世之前,prelude(廣東及港澳地區稱“披露”)就是本田coupe車款的代表,也是展現本田各種黑科技的試驗田。DOHC VTEC、雙搖臂懸架及四輪轉向系統等配置早早就出現在prelude之上,中文意謂“前奏曲”的prelude名字原為豐田擁有,當本田於70年代末期決定以此為新車型號后才發現早已被豐田註冊了名字。本來最簡單的方法就是改名,但當年本田執意鍾情於prelude這個名字,於是主動向豐田提出轉讓要求,結果一輪商議后prelude終於歸入本田名下。雖然雙方未有提到當中細節,不過相信離不開用金錢來解決,而這個買回來的名字就一直使用到2001年第五代prelude停產為止,歷時23年。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!

概述
Java 里的攔截器是動態攔截 action 調用的對象。
可以在Controller 中的方法執行之前與執行之後,及頁面显示完畢后,執行指定的方法,自定義的攔截器必須實現HandlerInterceptor 接口。
在業務處理器處理請求之前被調用
在業務處理器處理完請求后
在 DispatcherServlet 完全處理完請求后被調用
創建一個類實現 HandlerInterceptor 接口
配置文件當中添加攔截器
內部源碼分析
第 2 個返回 false
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!

全稱:django-rest framework
接口:什麼是接口、restful接口規範(協議)
CBV(基於FBV的基礎上形成)、CBV生命周期源碼—-基於restful規範下的CBV接口
請求生命周期:請求組件、解析組件、響應組件
序列化組件(序列化、反序列化簡單來說就是對象轉為字符串、字符串轉為對象,目的是為傳輸數據(傳給別的語言或者存儲))
三大認證(重中之重):認證(用戶是否合法)、權限(管理員、普通用戶)、頻率(次數過多限制)
其他組件(過濾、篩選、排序、分頁、路由)
概念:聯繫兩個物質的媒介,完成信息的交互。在web程序中,聯繫前台頁面與後台數據庫的媒介。
web接口組成:
url:長得像返回數據的url鏈接。如api.baidu.map/search
(www.baidu.com不叫接口,叫url鏈接,訪問只能拿到主頁。api.baidu.map/search是接口,返回的是json數據)
請求參數:前台按照指定的key提供數據給後台。(必須是給定的,這樣後台才能以此提取數據再到數據庫查詢返回)
響應數據:後台與數據庫交互后,將數據反饋給前台。
因此,接口就是帶着請求參數訪問能夠得到響應數據的url鏈接。
接口 = url + 請求參數 + 響應數據
接口規範:不同後台語言,使用同樣的接口返回同樣的數據。
如何寫接口:要寫url和響應數據。如果將請求參數也加上,就是在寫接口文檔。
兩大部分:
(1)url
1)用api關鍵字標識接口url。方式1:api.baidu.com;方式2:www.baudu.com/api
2)接口數據安全性決定優先選用https協議。
3)如果一個接口有多版本存在,需要在url中標識體現。如下的v1和v2
api.baidu.com/v1/…. api.baidu.com/v2/….
4)操作中的數據稱為資源,在url中資源一般採用複數形式,一個接口可以概括對該資源的多種操作方式。(一個url對應一個類,類裏面可以有多個請求方法)
可以對操作隱藏,並且復用性更強(寫動作了,只能適用這一個動作,不寫其他動作都可以用)如api.baidu.com/books api.baidu.com/books/(pk)
5)請求方式有多種,用一個url處理如何讓保證不混亂——通過不同的請求方式標識不同操作資源的方式
/books get 獲取所有
/books post 增加一個(多個)
/books/(pk) delete 刪除一個
/books/(pk) put 整體更新一個 #改一個用戶
/books/(pk) patch 局部更新一個 #改一個用戶的密碼
6)資源往往涉及數據的各種操作方式:篩選、排序、限制
api.baidu.com/books/?search=寶馬&ordering=-price&limit=3
(2)響應數據
1)http請求的響應會有響應狀態碼,接口用來返回操作的資源數據,也有自己操作數據結果的資源狀態碼(status 0代表操作資源成功,1代表操作失敗,2代表操作成功,但沒匹配結果)
注:資源狀態碼和http狀態碼不一樣,為前後台的約定
2)資源狀態碼的文字提示。
status ok “賬號有誤或者密碼有誤”
3)資源本身
results
注:刪除資源成功不做任何數據返回(只返回空字符串,連狀態碼、狀態信息都不返回)
4)不能直接返回的資源(子資源、圖片、視頻等資源),返回該資源的url鏈接。
https://api.baidu.com/v1/books?limit=3 get|post|delete|put|patch { “status” : 0, “msg” : “ok”, “results”: [ { “title”: “三國”, “price”: 78, “img”: “https://.....” } ] }
(1)項目準備:
|
1.分發路由 在項目文件夾的urls複製一份到應用文件夾中。然後在項目文件夾的urls分發路由給app:導入include,然後url(r’^api/’, include(‘api.urls’))。再在app文件夾的urls.py中分發路由給CBV 2.視圖 在應用中分發路由前,先寫類視圖 from django.http import JsonResponse from django.views import View class Book(View): def get(self, request, *args, **kwargs): return JsonResponse('get ok', safe=False) def post(self, request, *args, **kwargs): return JsonResponse('get ok', safe=False) #safe默認為true,要返回字典。不是字典否則拋異常。
3.在應用urls下分發路由 from django.conf.urls import url from . import views #注意在應用中導入視圖都是. 從當前應用中導入 urlpatterns = [ url(r'^books/', views.Book.as_view()), ]
4.定義模型類 (1)models.py中定義類 from django.db import models class Book(models.Model): title = models.CharField(max_length=64) price = models.DecimalField(max_digits=5, decimal_places=2) #整數、小數位 class Meta: #嵌套類(給上級類添加功能或指定標準) db_table = 'book' #自定義數據庫表名 verbose_name = "book" #給模型起個可讀的名字,默認是複數 verbose_name_plural = verbose_name #取消上面的複數 def __str__(self): #显示的內容 return '<<%s>>' % self.title
(2)數據庫遷移 進入django的shell環境中:Tools—-> run manage.py task 在shell環境中生成遷移文件:makemigrations。然後遷移:migrate
5.生成admin (1)在amin.py中註冊並且導入模型 from django.contrib import admin port models admin.site.register(models.Book)
(2)創建用戶 在shell環境中:createsuper創建超級用戶,然後輸入用戶密碼(郵箱不用)
|
(2)CBV的請求生命周期
請求如何到CBV下的get和post
a.請求過來,項目文件中路由分發給應用api的路由
b.應用分發路由走as_view函數。
views.Book.as_view() 保存一系列數據(request、args、**kwargs等)給Book對象,然後都給dispatch進行路由分發。
dispatch乾的事:判斷請求方式是否支持,然後返回(通過getattr)支持的這些請求方法(get、post等,在視圖中自定義get、post的返回值)的結果。
c.通過dispatch就執行了CBV下請求方式的結果,返回結果
六大基礎接口:獲取一個、獲取所有、增加一個、刪除一個、整體更新一個、局部更新一個
十大接口:6大基礎、群增、群刪、整體群改、局部群改
|
1.在應用的urls.py下分發路由 url(r’^books/$’, views.Book.as_view()), #必須要加$,否則後面匹配不到 url(r’^books/(?P<pk>.*)/$’, views.Book.as_view()),有名分組 在視圖函數中通過kwargs.get(pk)取到匹配的值
2.在views.py里寫邏輯 class Book(View):
def get(self, request, *args, **kwargs): pk = kwargs.get(‘pk’) #獲取參數 if not pk: #群查接口 #操作數據庫 book_obj_list = models.Book.objects.all() #序列化過程 book_list = [] for obj in book_obj_list: #將查到的對象序列化 dic = {} dic[‘title’] = obj.title dic[‘price’] = obj.price book_list.append(dic) return JsonResponse({ ‘status’ : 0, “msg” : “ok”, “results”: book_list, }, json_dumps_params={‘ensure_ascii’:False}) else: #單查接口 book_dic = models.Book.objects.filter(pk=pk).values( ‘title’, ‘price’).first() if book_dic: return JsonResponse({ ‘status’: 0, “msg”: “ok”, “results”: book_dic, }, json_dumps_params={‘ensure_ascii’: False})
return JsonResponse({ ‘status’: 2, “msg”: “no results”, }, json_dumps_params={‘ensure_ascii’: False})
def post(self, request, *args, **kwargs): #前台通過urlencoded方式提交數據 try: book_obj = models.Book.objects.create(**request.POST.dict()) #create創建對象。將request.POST中存放的提交的關鍵詞參數轉化為字典以**方式傳進去。沒傳參數,這邊會報錯。 if book_obj: return JsonResponse({ ‘status’: 0, “msg”: “ok”, “results”: {‘title’:book_obj.title, “price”:book_obj.price} }, json_dumps_params={‘ensure_ascii’: False}) except: #健壯性 return JsonResponse({ ‘status’: 1, “msg”: “wrong params”, }, json_dumps_params={‘ensure_ascii’: False}) return JsonResponse({ #可能操作數據庫失敗了 ‘status’: 2, “msg”: “created failed”, }, json_dumps_params={‘ensure_ascii’: False})
|
JsonResponse返回時,中文會變成unicode,要加json_dumps_params={‘ensure_ascii’:False}選項。但在linux環境下的火狐瀏覽器,加了是亂碼。
filter返回queryset對象,對象里是個列表(表名:對象信息(有自定義str就是自定義的信息))。first取里第一個對象(相當於print(第一個對象))values展示對應的對象里的值
<QuerySet [<Book: <<三國演義>>>]> #直接.filter
<<三國演義>> #.first()
<QuerySet [{‘title’: ‘三國演義‘, ‘price’: Decimal(‘56.00’)}]> #.values(‘title’,’price’)
{‘title’: ‘三國演義‘, ‘price’: Decimal(‘56.00’)} #.values.first() 是個字典
上面序列化的工作很麻煩。drf就是為了方便序列化的。
postman可以完成不同方式的請求:get、post、put等
postman發送數據包有三種方式:form-data、urlencoded、json. 原生django對urlencoded數據提交兼容。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!
文章每周持續更新,原創不易,「三連」讓更多人看到是對我最大的肯定。可以微信搜索公眾號「 後端技術學堂 」第一時間閱讀(一般比博客早更新一到兩篇)
”
對於一般的語言使用者來說 ,20% 的語言特性就能夠滿足 80% 的使用需求,剩下在使用中掌握。基於這一理論,Go 基礎系列的文章不會刻意追求面面俱到,但該有知識點都會覆蓋,目的是帶你快跑趕上 Golang 這趟新車。
Hurry up , Let’s go !
前面我們學習過 Golang 中基礎數據類型,比如內置類型 int string bool 等,其實還有一些複雜一點點,但很好用的複合類型,類似 C 中的數組和 struct、C++ 中的 map ,今天我們就來學習 Go 中的複合類型。
通過本文的學習你將掌握以下知識:
指針不保存實際數據的內容,而是保存了指向值的內存地址 。用 & 對變量取內存地址,用 * 來訪問指向的內存。這點和 C 中的指針是一樣,唯一不同的是 Go 中的指針不能運算。
a := 3
pa := &a // 用 `&` 對變量取內存地址
fmt.Println("point", a, *pa) // 用 `*` 來訪問指向的內存
只聲明沒賦值的指針值是 nil ,代表空指針。
var a0 *int // 只聲明沒賦值的指針是nil
if a0 == nil {
fmt.Println("point", "it is nil point")
}
與C中的結構體類似, 結構體是一種聚合的數據類型,是由零個或多個任意類型的值聚合成的實體。每個值稱為結構體的成員,看例子:
type Test struct {
a int
b int
}
語法上的不同看到了嗎? 每個結構體字段之後沒有分號,沒有分號寫起來還是很舒服的。
可以在定義的時候初始化
test := Test{1, 2} // 定義結構體變量並初始化
初始化部分結構體字段
t2 = Test{a: 3} //指定賦值Test.a為3 Test.b隱式賦值0
隱式初始化
t3 = Test{} // .a .b都隱式賦值0
多個變量可以分組一起賦值
var (
t1 = Test{8, 6}
t2 = Test{a: 3} //指定賦值Test.a Test.b隱式賦值0
t3 = Test{} // .a .b都隱式賦值0
pt4 = &Test{8, 6} // 指針
)
通過 . 運算來訪問結構體成員,不區分結構體類型或是結構體指針類型。
fmt.Println("struct", st0.a, st0.b) // 通過 . 運算來訪問結構體成員
對於只聲明沒賦值的結構體,其內部變量被賦予零值,下面我們聲明了 st0 但沒有對其賦值。
var st0 Test
fmt.Println("struct", st0.a, st0.b) //輸出:struct 0 0
數組是一個由固定長度的特定類型元素組成的序列,一個數組可以由零個或多個元素組成。 數組可以用下標訪問元素,下標從 0 開始。
數組聲明后賦值
var strarr [2]string // 數組聲明語法
strarr[0] = "ready"
strarr[1] = "go"
聲明賦值同時完成
intarr := [5]int{6, 8, 9, 10, 7} // 聲明賦值同時完成
對於確定初始值個數的數組,可以省略數組長度
intarr := [...]int{6, 8, 9, 10, 7} // 聲明賦值同時完成
切片是變長的序列,序列中每個元素都有相同的類型。slice 語法和數組很像,只是沒有固定長度而已,切片底層引用一個數組對象,修改切片會修改原數組。
通過切片可以訪問數組的部分或全部元素,正因為切片長度不是固定的,因此切片比數組更加的常用。
簡短聲明並初始化切片
s0 := []int{1, 2, 3, 4, 5, 6} // 簡短聲明加賦值
聲明后再初始化
var s []int // 聲明切片s
s = s0 // 用切片s0初始化切片s
聲明並初始化切片
var s00 []int = s0 // 用切片s0初始化切片s
切片的零值是 nil
// 切片的零值是nil 空切片長度和容量都是0
var nilslice []int
if nilslice == nil {
fmt.Println("slice", "nilslice is nil ", len(nilslice), cap(nilslice))
}
除了上述的常規初始化方法,還可以用 make 內置函數來創建切片
// 內建函數make創建切片,指定切片長度和容量
// make 函數會分配一個元素為零值的數組並返回一個引用了它的切片
s2 := make([]int, 4, 6) //創建元素都是0的切片s2, 長度為4,容量為6 第三個參數可以省略
fmt.Println("slice", len(s2), cap(s2), s2)
長度表示切片中元素的數目,可用內置函數 len 函數得到。
容量表示切片中第一個元素到引用的底層數組結尾所包含元素個數,可用內置函數 cap 求得。
切片區間遵循「左閉右開」原則,
s0 := [5]int{6, 8, 9, 10, 7} // 數組定義
var slice []int = intarr[1:4] // 創建切片slice 包含數組子序列
默認上下界。切片下界的默認值為 0,上界默認是該切片的長度。
fmt.Println("slice", s0[:], s0[0:], s0[:5], s0[0:5]) // 這四個切片相同
append 函數用於在切片末尾追加新元素。
添加元素也分兩種情況。
s2 := make([]int, 4, 6) //創建元素都是0的切片s2, 長度為4,容量為6 第三個參數可以省略
s22 := append(s2, 2) // append每次都是在最後添加,所以此時,s21 s22指向同一個底層數組
fmt.Println(s21, s22) // [0 0 0 0 2] [0 0 0 0 2]
此時會分配新的數組空間,並返回指向這個新分配的數組的切片。
下面例子中 s24 切片已經指向新分配的數組,s22 依然指向的是原來的數組空間,而 s24 已經指向了新的底層數組。
s24 := append(s2, 1, 2, 3)
fmt.Println(s24, s22) // s24 [0 0 0 0 1 2 3] [0 0 0 0 2]
可以定義切片的切片,類似其他語言中的二維數組用法。參考代碼:
s3 := [][]int{
{1, 1, 1},
{2, 2, 2},
}
fmt.Println(s3, s3[0], len(s3), cap(s3)) // 輸出: [[1 1 1] [2 2 2]] [1 1 1] 2 2
在 Go 中 map 是鍵值對類型,代表 key 和value 的映射關係,一個map就是一個哈希表的引用 。
下面這樣定義並初始化一個 map 變量
m0 := map[int]string{
0: "0",
1: "1",
}
也可以用內置 make 函數來初始化一個 map 變量,後續再向其中添加鍵值對。像下面這樣:
m1 := make(map[int]string) // make 函數會返回給定類型的映射,並將其初始化備用
if m1 != nil {
fmt.Println("map", "m1 is not nil", m1) // m1 不是nil
}
m1[0] = "1"
m1[1] = "2"
注意:只聲明不初始化的map變量是 nil 映射,不能直接拿來用!
var m map[int]string // 未初始化的m零值是nil映射
if m == nil {
fmt.Println("map", "m is nil", m)
}
//m[0] = "1" // 這句引發panic異常, 映射的零值為 nil 。nil映射既沒有鍵,也不能添加鍵。
使用語法:vaule= m[key] 獲取鍵 key 對應的元素 vaule 。
上面我們只用了一個變量來獲取元素,其實這個操作會返回兩個值,第一個返回值代表讀書的元素,第二個返回值是代表鍵是否存在的 bool 類型,舉例說明:
v, st := m1[0] // v是元素值,下標對應的元素存在st=true 否則st=false
_, st1 := m1[0] // _ 符號表示忽略第一個元素
v1, _ := m1[0] // _ 符號表示忽略第二個元素
fmt.Println(v, st, v1, st1, m1[2]) // m1[2]不存在,返回元素string的零值「空字符」
內置函數 delete 可以刪除 map 元素,舉例:
delete(m1, 1) // 刪除鍵是 1 的元素
range 用於遍歷 切片 或 映射。
當使用for 循環和 range 遍曆數組或切片時,每次迭代都會返回兩個值。第一個值為當前元素的下標,第二個值為該下標所對應元素的一份副本。
s1 := []int{1, 2, 3, 4, 5, 6}
for key, vaule := range s1 {
fmt.Println("range", key, vaule)
}
for key := range s1 { // 只需要索引,忽略第二個變量即可
fmt.Println("range", key)
}
for _, vaule := range s1 { // 只需要元素值,用'_'忽略索引
fmt.Println("range", vaule)
}
當使用for 循環和 range 遍歷map 時,每次迭代都會返回兩個值。第一個值為當前元素 key , 第二個值是 value。
m0 := map[int]string{
0: "0",
1: "1",
}
fmt.Println("map", m0)
for k, v := range m0 { // range遍歷映射,返回key 和 vaule
fmt.Println("map", "m0 key:", k, "vaule:", v)
}
通過本文的學習,我們掌握了 Golang 中基本的控制流語句,利用這些控制語句加上一節介紹的變量等基礎知識,可以構成豐富的程序邏輯,你就能用 Golang 來做一些有意思的事情了。
感謝各位的閱讀,文章的目的是分享對知識的理解,技術類文章我都會反覆求證以求最大程度保證準確性,若文中出現明顯紕漏也歡迎指出,我們一起在探討中學習.
今天的技術分享就到這裏,我們下期再見。
創作不易,白票不是好習慣,如果在我這有收穫,動動手指「點贊」「關注」是對我持續創作的最大支持。
可以微信搜索公眾號「 後端技術學堂 」回復「資料」「1024」有我給你準備的各種編程學習資料。文章每周持續更新,我們下期見!
”
本文使用 mdnice 排版
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!

@
目錄
前面幾篇文章,學習了Spring IOC、Bean實例化過程、AOP、事務的源碼和設計思想,了解了Spring的整體運行流程,但如果是web開發,那麼必不可少的還有Spring MVC,本篇主要分析在請求調用過程中SpringMVC的實現原理,通過本篇要搞懂它是怎麼解決請求、參數、返回值映射等問題的。
我們都知道前端調用後端接口時,都會通過Servlet進行轉發,而Servlet的聲明周期包含下面四個階段:
前兩個階段在Spring啟動階段就做好了(init根據配置可能是第一次請求時才會調用),銷毀是服務關閉的時候進行,本文主要分析的就是請求執行階段。我們知道SpringMVC的核心就是DispatcherServlet,該類是對Servlet的擴展,所以直接從該類的service方法開始,但在此類中沒有service方法,那肯定是在其父類中,我們先來看看其繼承體系:
逐個往上找,在FrameworkServlet方法中就有一個service方法:
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
但其主要還是調用父類HttpServlet中的方法,而該類又會根據不同的請求方式會調到子類中,最後的核心方法就是DispatcherServlet中的doDispatch方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//異步管理
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//文件上傳
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//這個方法很重要,重點看
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//獲取跟HandlerMethod匹配的HandlerAdapter對象
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//前置過濾器,如果為false則直接返回
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//調用到Controller具體方法,核心方法調用,重點看看
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//中置過濾器
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//視圖渲染及後置過濾器執行
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
MVC的所有處理邏輯都在這個方法中,先總結一下這個方法的實現邏輯,首先根據請求的url拿到緩存中的HandlerMethod對象和執行鏈對象,HandlerMethod中封裝了controller對象、方法對象和方法參數等信息,執行鏈則是包含了一個個HandlerInterceptor攔截器;然後再通過HandlerMethod拿到對應的HandlerAdapter,這個對象的作用就是去適配我們的controller;準備工作做完后,首先會執行前置過濾,如果被攔截則直接返回,否則就去調用controller中的方法執行我們的業務邏輯並返回一個ModelView對象;接着執行中置過濾器,以及處理全局異常捕獲器捕獲到異常;最後進行視圖渲染返回並執行後置過濾器進行資源釋放等工作。
以上就是MVC的整體執行流程,下面就逐個來分析,首先進入getHandler方法:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//handlerMappering實例
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
//獲取HandlerMethod和過濾器鏈的包裝類
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
是委託給HandlerMapping對象的,這是一個接口,主要的實現類是RequestMappingHandlerMapping,同樣先來看看其繼承體系:
這個類是管理請求和處理類之間的映射關係的,你是否疑惑它是在哪裡實例化的呢?下面先來看看MVC組件的初始化。
這裏我以自動化配置的註解方式說明,Spring提供了一個@EnableWebMvc,通過前面的學習我們知道在這個註解中必定導入了一個配置類,點進去可以看到是DelegatingWebMvcConfiguration,這個類就是負責MVC的組件和擴展實現的初始化,其本身我們先不看,先看其父類WebMvcConfigurationSupport,這個類我們應該不陌生,要做一些自定義擴展時就需要繼承該類(如攔截器Interceptor),同樣作用的類還有WebMvcConfigurerAdapter,這個類是對前者相對安全的擴展,為什麼是相對安全呢?因為繼承前者會導致自動配置失效,而使用後者則不必擔心此問題,只需要在類上加上@EnableWebMvc註解。
在WebMvcConfigurationSupport中我們可以看到很多@Bean標註的方法,也就是mvc組件的實例化,這裏主要看看requestMappingHandlerMapping,其餘的可自行閱讀理解,也就是一些Bean的註冊:
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors());
mapping.setContentNegotiationManager(mvcContentNegotiationManager());
mapping.setCorsConfigurations(getCorsConfigurations());
......省略
return mapping;
}
這裏主要看getInterceptors方法如何獲取攔截器的:
protected final Object[] getInterceptors() {
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
//鈎子方法,需要自己定義
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
第一次進來會調用addInterceptors添加攔截器,這是一個模板方法,在子類DelegatingWebMvcConfiguration中實現:
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
}
}
可以看到最終是調用WebMvcConfigurer的addInterceptors方法,也就是我們對WebMvcConfigurerAdapter的自定義擴展。看到這裏我們應該明白了MVC的組件是如何添加到IOC容器中的,但是DispatcherServlet又是怎麼獲取到它們的呢?回到之前的代碼中,在DispatcherServlet這個類中有一個onRefresh方法,這個方法又調用了initStrategies方法完成了MVC九大組件的註冊:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
以initHandlerMappings為例,其它組件實現邏輯基本一樣。首先從IOC容器中拿到handlerMappings的所有實現類(WebMvcConfigurationSupport中注入的對象就在這裏被獲取到),若沒有,則從DispatcherServlet.properties配置文件中(這個配置在spring-webmvc工程下org/springframework/web/servlet/DispatcherServlet.properties)獲取默認的配置:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
但是onRefresh又是在什麼時候調用的呢?有兩個地方,一個是Servlet初始化時會調用到initWebApplicationContext進行容器的初始化,這個方法中就會觸發onRefresh;另外還有一個,在FrameworkServlet中有一個onApplicationEvent方法,而這個方法又會被內部類ContextRefreshListener調用,這個類實現了ApplicationListener接口,表示會接收容器刷新事件。
以上就就是MVC HandlerMapping組件的初始化邏輯,其它組件實現邏輯相同,下面不再分析。
回到getHandler方法,其調用的是AbstractHandlerMapping類的方法:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根據請求的uri拿到對應的HandlerMethod對象
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//獲取HandlerMethod和過濾器鏈的包裝類
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
//是否是跨域請求,就是查看request請求頭中是否有Origin屬性
if (CorsUtils.isCorsRequest(request)) {
//自定義的鈎子方法獲取跨域配置
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
//註解獲取跨域配置
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
//這裏設置了跨域的過濾器CorsInterceptor
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
先看AbstractHandlerMethodMapping.getHandlerInternal:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//從request對象中獲取uri,/common/query2
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
//根據uri從映射關係中找到對應的HandlerMethod對象
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
//把Controller類實例化
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 根據url拿到對應的RequestMappingInfo
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
//如果兩個RequestMappinginfo什麼都相同,報錯
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
// 拿到匹配的RequestMappingInfo對象,有可能url相同,@RequestMapping的屬性(請求方式、參數等)匹配不上
T match = getMatchingMapping(mapping, request);
if (match != null) {
//RequestMappingInfo對象和HandlerMethod對象封裝到Match對象中,其實就是註解屬性和Method對象的映射
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
這裏邏輯很簡單,就是通過請求url從urlLookup中拿到對應的RequestMappingInfo(每一個 @RequestMapping對應一個RequestMappingInfo對象)對象,再根據RequestMappingInfo對象從mappingLookup拿到對應的HandlerMethod並返回。
但這裏你可能會比較好奇urlLookup和mappingLookup從哪裡來的,仔細觀察你會發現當前這個類實現了一個接口InitializingBean,實現了這個接口的類會在該類的Bean實例化完成后調用afterPropertiesSet方法,上面的映射關係就是在這個方法中做的。實際上這個方法不止完成了上面兩個映射關係,還有下面兩個:
這裏就不展開分析了,奉上一張時序圖,讀者可根據下面的時序圖自行分析:
拿到HandlerMethod對象后,又會通過getHandlerExecutionChain方法去獲取到所有的HandlerInterceptor攔截器對象,並連同HandlerMethod對象一起封裝為HandlerExecutionChain。之後是獲取跨域配置,這裏不詳細分析。
拿到HandlerExecutionChain對象后返回到doDispatch方法,又調用了getHandlerAdapter
方法拿到HandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//根據handlerMethod對象,找到合適的HandlerAdapter對象,這裏用到了策略模式
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
}
這裏的handlerAdapters變量值從哪裡來?相信不用我再分析,主要看這裏的設計思想,典型的策略模式。
之後調用完前置過濾器后,才是真正調用我們controller方法的邏輯,通過HandlerAdapter.handle去調用,最終會調用到ServletInvocableHandlerMethod.invokeAndHandle:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//具體調用邏輯,重點看
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//返回值處理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
這個方法裏面主要看invokeForRequest和handleReturnValue的調用,前者是完成參數綁定並調用controller,後者則是對返回值進行處理並封裝到ModelAndViewContainer中。先來看invokeForRequest:
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//獲取參數數組
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
doInvoke就是完成反射調用,主要還是看參數綁定的實現邏輯,在getMethodArgumentValues方法中:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
if (ObjectUtils.isEmpty(getMethodParameters())) {
return EMPTY_ARGS;
}
//入參的包裝類,裡面包裝了參數類型,參數名稱,參數註解等等信息
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
//設置參數名稱解析器
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
//典型的策略模式,根據parameter能否找到對應參數的處理類,能找到就返回true
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//具體參數值解析過程,重點看看
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled..
if (logger.isDebugEnabled()) {
String error = ex.getMessage();
if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, error));
}
}
throw ex;
}
}
return args;
}
因為參數類型非常多,同時還會伴隨各種註解,如:@RequestBody、@RequestParam、@PathVariable等,所以參數解析的工作是非常繁雜的,同時還要考慮到擴展性,所以SpringMVC依然採用了策略模式來完成對各種參數類型的解析綁定,其頂層接口就是HandlerMethodArgumentResolver,而默認SpringMVC提供的解析方式就高達20多種:
上面是類圖,讀者可根據自己熟悉的參數類型找到對應的類進行分析,最核心的還是要掌握這裏的設計思想。
接着方法調用完成后就是對返回值的處理,同樣的,返回值類型也是非常多,也可以使用各種註解標註,所以也是使用策略模式實現,其頂層接口是HandlerMethodReturnValueHandler,實現類如下:
調用完成之後就是執行後續操作了:執行中置過濾器、處理全局異常、視圖渲染以及執行後置過濾器,這些與主流程沒有太大關係,本篇不展開分析了,最後是MVC的執行時序圖:
本篇是Spring核心原理系列的最後一篇,前前後后花了一個月時間,終於從宏觀上大致上理解了Spring的實現原理和運行機制,明白了之前項目中一些坑是如何產生的,最主要的是學到設計模式的運用以及如何利用Spring的一些常用的擴展點進行自定義擴展。但對於Spring這個龐大的體系來說,還有很多是要去理解學習的,尤其是設計思想,只有長期琢磨才能深刻的理解掌握。在我之前的文章中包括本篇還有很多沒分析到的細節,在後面我會不定期分享出來。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!
摘錄自2018年9月9日蘋果日報日本報導
日本農業部門今(9)日證實,當地出現26年來首起豬瘟感染病例,懷疑是進口豬肉或野豬肉帶入病毒導致感染。
發現疫情的是一間位於岐阜市的養豬場,3日到8日發現有80頭豬死亡。檢驗後證實,這些豬隻感染豬瘟,此病毒與最近在中國爆發的非洲豬瘟並不相同,也不會傳染給人類。
根據日本家畜傳染病預防法規定,只要養豬場發現有豬隻感染豬瘟,就必須撲殺養豬場內所有豬隻。岐阜縣政府今日上午開始撲殺養豬場內的610頭豬隻,作業將持續到明天上午6時。由於豬瘟在豬隻間具傳染性,岐阜縣為預防豬瘟疫情擴大,將這間養豬場半徑10公里內劃為「禁止搬出區域」;並禁止這個區域內的其他3間養豬場出貨。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!

光陽工業(KYMCO)12 日正式在台發表 Ionex 車能網商業版,提供 4 大套裝方案,切入全球商用電動機車領域;董事長柯勝峯更宣布進軍印度這個全球最大二輪車市場,並且全面拓展中國市場。
隨著環保意識不斷提升,電動化成為現代交通工具最重要的轉變,有效落實城市永續經營的目標。其中,商用電動車扮演極為關鍵的角色,據統計全球約有 1.5 億輛機車,其中約 500 萬輛機車為商業用途,商用機車佔比雖低,但每日騎乘距離是一般機車的 7 倍,維修換車頻率則是一般機車的 2 倍,能源消耗佔比較整體機車多 2 成。因此採行電動車,能夠真正協助企業進行營運模式的發展與轉型。
Ionex 車能網商業版可針對任何企業或政府機構的特定需求,進行量身訂製並快速導入;主要向客戶提供「基礎設施」、「商用車隊」、「共享車輛」、「大眾運輸」4 個套裝方案,內容含括電動機車、抽取式電池、能源交換站、作業系統、管理軟體、手機 App 以及其他客製化服務等等,視需求做出調整,以展現 Ionex 車能網的系統彈性與發展能力:
今日的發表會上更展示 2 款商用電動機車、以及 1 款電動輔助自行車。
目前在企業客戶方面,光陽已有大型零售的盒馬鮮生、車輛共享的騎電科技、能源服務的張飛充電;而在發展智慧城市,光陽與中國的江蘇常州市、福建寧德市、浙江衢州市、廣東深圳市合作。光陽更將在下週於印度首都新德里,正式宣布進軍印度市場,與當地的合作夥伴加速拓展 Ionex 車能網。
(合作媒體:。圖片來源:)
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!