標籤: 如何寫文案

  • drf之框架基礎

    drf之框架基礎

    (一)drf基礎

    全稱:django-rest framework

    接口:什麼是接口、restful接口規範(協議)

    CBV(基於FBV的基礎上形成)、CBV生命周期源碼—-基於restful規範下的CBV接口

    請求生命周期:請求組件、解析組件、響應組件

    序列化組件(序列化、反序列化簡單來說就是對象轉為字符串、字符串轉為對象,目的是為傳輸數據(傳給別的語言或者存儲))

    三大認證(重中之重):認證(用戶是否合法)、權限(管理員、普通用戶)、頻率(次數過多限制)

    其他組件(過濾、篩選、排序、分頁、路由)

     

    1.接口

    概念:聯繫兩個物質的媒介,完成信息的交互。在web程序中,聯繫前台頁面後台數據庫的媒介。

    web接口組成:

    url:長得像返回數據的url鏈接。如api.baidu.map/search

    www.baidu.com不叫接口,叫url鏈接,訪問只能拿到主頁。api.baidu.map/search是接口,返回的是json數據)

    請求參數:前台按照指定的key提供數據給後台。(必須是給定的,這樣後台才能以此提取數據再到數據庫查詢返回)

    響應數據:後台與數據庫交互后,將數據反饋給前台。

    因此,接口就是帶着請求參數訪問能夠得到響應數據的url鏈接。

    接口 = url + 請求參數 + 響應數據

     

     

    2.restful接口規範

    接口規範:不同後台語言,使用同樣的接口返回同樣的數據。

    如何寫接口:要寫url和響應數據。如果將請求參數也加上,就是在寫接口文檔。

     

    兩大部分:

    1url

    1)用api關鍵字標識接口url。方式1api.baidu.com;方式2:www.baudu.com/api

    2)接口數據安全性決定優先選用https協議

    3)如果一個接口有多版本存在,需要在url中標識體現。如下的v1v2

    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)響應數據

    1http請求的響應會有響應狀態碼,接口用來返回操作的資源數據,也有自己操作數據結果的資源狀態碼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://.....”   
        }
      ]
    }

     

    3.django流程

    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.定義模型類

    1models.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)數據庫遷移

    進入djangoshell環境中: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創建超級用戶,然後輸入用戶密碼(郵箱不用)

     

     

     2CBV的請求生命周期

    請求如何到CBV下的getpost

    a.請求過來,項目文件中路由分發給應用api的路由

    b.應用分發路由走as_view函數。

    views.Book.as_view()  保存一系列數據(requestargs**kwargs等)給Book對象,然後都給dispatch進行路由分發。

    dispatch乾的事:判斷請求方式是否支持,然後返回(通過getattr)支持的這些請求方法(getpost等,在視圖中自定義getpost的返回值)的結果。

    c.通過dispatch就執行了CBV下請求方式的結果,返回結果

     

     

    4.django原生的接口、序列化

      六大基礎接口:獲取一個、獲取所有、增加一個、刪除一個、整體更新一個、局部更新一個

     十大接口: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可以完成不同方式的請求:getpostput

    postman發送數據包有三種方式:form-dataurlencodedjson. 原生djangourlencoded數據提交兼容。

     

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

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 『圖論』LCA 最近公共祖先

    『圖論』LCA 最近公共祖先

    概述篇

    LCA (Least Common Ancestors) ,即最近公共祖先,是指這樣的一個問題:在一棵有根樹中,找出某兩個節點 uv 最近的公共祖先。

    LCA 可分為在線算法離線算法

    • 在線算法:指程序可以以序列化的方式一個一個處理輸入,也就是說在一開始並不需要知道所有的輸入。
    • 離線算法:指一開始就需要知道問題的所有輸入數據,而在解決一個問題后立即輸出結果。

    算法篇

    對於該問題,很容易想到的做法是從 u、v 分別回溯到根節點,然後這兩條路徑中的第一個交點即為 u、v 的最近公共祖先,在一棵平衡二叉樹中,該算法的時間複雜度可以達到 O(logn)O(log⁡n) ,但是對於某些退化為鏈狀的樹來說,算法的時間複雜度最壞為 O(n)O(n) ,顯然無法滿足更高頻率的查詢。

    本節將介紹幾種比較高效的算法來解決這一問題,常見的算法有三種:在線 DFS + ST 算法、倍增算法、離線 Tarjan 算法。

    接下來我們來一一解釋這三種 /* 看似高深,其實也不簡單 */ 的算法。

    在線 DFS + ST 算法

    首先看到 ST 你會想到什麼呢?(腦補許久都沒有想到它會是哪個單詞的縮寫)

    看過前文 『數據結構』RMQ 問題 的話你便可以明白 ST算法 的思路啦~

    So ,關於 LCA 的這種在線算法也是可以建立在 RMQ 問題的基礎上咯~

    我們設 LCA(T,u,v) 為在有根樹 T 中節點 u、v 的最近公共祖先, RMQ(A,i,j) 為線性序列 A 中區間 [i,j] 上的最小(大)值。

    如下圖這棵有根樹:

    我們令節點編號滿足父節點編號小於子節點編號(編號條件)

    可以看出 LCA(T,4,5) = 2, LCA(T,2,8) = 1, LCA(T,3,9) = 3

    設線性序列 A 為有根樹 T 的中序遍歷,即 A = [4,2,5,1,8,6,9,3,7]

    由中序遍歷的性質我們可以知道,任意兩點 u、v 的最近公共祖先總在以該兩點所在位置為端點的區間內,且編號最小。

    舉個栗子:

    假設 u = 8, v = 7 ,則該兩點所確定的一段區間為 [8,6,9,3,7] ,而區間最小值為 3 ,也就是說,節點 3u、v 的最近公共祖先。

    解決區間最值問題我們可以採用 RMQ 問題中的 ST 算法

    但是在有些問題中給出的節點並不一定滿足我們所說的父節點編號小於子節點編號,因此我們可以利用節點間的關係建圖,然後採用前序遍歷來為每一個節點重新編號以生成線性序列 A ,於是問題又被轉化為了區間最值的查詢,和之前一樣的做法咯~

    時間複雜度: n×O(logn)n×O(log⁡n) 預處理 + O(1)O(1) 查詢

    想了解 RMQ 問題 的解法可以戳上面的鏈接哦~

    以上部分介紹了 LCA 如何轉化為 RMQ 問題,而在實際中這兩種方案之間可以相互轉化

    類比之前的做法,我們如何將一個線性序列轉化為滿足編號條件的有根樹呢?

    1. 設序列中的最小值為 AkAk ,建立優先級為 AkAk 的根節點 TkTk
    2. 將 A[1…k−1]A[1…k−1] 遞歸建樹作為 TkTk 的左子樹
    3. 將 A[k+1…n]A[k+1…n] 遞歸建樹作為 TkTk 的右子樹

    讀者可以試着利用此方法將之前的線性序列 A = [4,2,5,1,8,6,9,3,7] 構造出有根樹 T ,結果一定滿足之前所說的編號條件,但卻不一定唯一。

    離線 Tarjan 算法

    Tarjan 算法是一種常見的用於解決 LCA 問題的離線算法,它結合了深度優先搜索與並查集,整個算法為線性處理時間。

    首先來介紹一下 Tarjan 算法的基本思路:

    1. 任選一個節點為根節點,從根節點開始
    2. 遍歷該點 u 的所有子節點 v ,並標記 v 已經被訪問過
    3. 若 v 還有子節點,返回 2 ,否則下一步
    4. 合併 v 到 u 所在集合
    5. 尋找與當前點 u 有詢問關係的點 e
    6. 若 e 已經被訪問過,則可以確定 u、e 的最近公共祖先為 e 被合併到的父親節點

    偽代碼:

    Tarjan(u)               // merge 和 find 為並查集合併函數和查找函數
    {
        for each(u,v)       // 遍歷 u 的所有子節點 v
        {
            Tarjan(v);      // 繼續往下遍歷
            merge(u,v);     // 合併 v 到 u 這一集合
            標記 v 已被訪問過;
        }
        for each(u,e)       // 遍歷所有與 u 有查詢關係的 e
        {
            if (e 被訪問過)
                u, e 的最近公共祖先為 find(e);
        }
    }
    C++
    

    感覺講到這裏已經沒有其它內容了,但是一定會有好多人沒有理解怎麼辦呢?

    我們假設在如下樹中模擬 Tarjan 過程(節點數量少一點可以畫更少的圖o( ̄▽ ̄)o)

    存在查詢: LCA(T,3,4)、LCA(T,4,6)、LCA(T,2,1)

    注意:每個節點的顏色代表它當前屬於哪一個集合,橙色線條為搜索路徑,黑色線條為合併路徑。

    當前所在位置為 u = 1 ,未遍歷孩子集合 v = {2,5} ,向下遍歷。

    當前所在位置為 u = 2 ,未遍歷孩子集合 v = {3,4} ,向下遍歷。

    當前所在位置為 u = 3 ,未遍歷孩子集合 v = {} ,遞歸到達最底層,遍歷所有相關查詢發現存在 LCA(T,3,4) ,但是節點 4 此時標記未訪問,因此什麼也不做,該層遞歸結束。

    遞歸返回,當前所在位置 u = 2 ,合併節點 3u 所在集合,標記 vis[3] = true ,此時未遍歷孩子集合 v = {4} ,向下遍歷。

    當前所在位置 u = 4 ,未遍歷孩子集合 v = {} ,遍歷所有相關查詢發現存在 LCA(T,3,4) ,且 vis[3] = true ,此時得到該查詢的解為節點 3 所在集合的首領,即 LCA(T,3,4) = 2 ;又發現存在相關查詢 LCA(T,4,6) ,但是節點 6 此時標記未訪問,因此什麼也不做。該層遞歸結束。

    遞歸返回,當前所在位置 u = 2 ,合併節點 4u 所在集合,標記 vis[4] = true ,未遍歷孩子集合 v = {} ,遍歷相關查詢發現存在 LCA(T,2,1) ,但是節點 1 此時標記未訪問,因此什麼也不做,該層遞歸結束。

    遞歸返回,當前所在位置 u = 1 ,合併節點 2u 所在集合,標記 vis[2] = true ,未遍歷孩子集合 v = {5} ,繼續向下遍歷。

    當前所在位置 u = 5 ,未遍歷孩子集合 v = {6} ,繼續向下遍歷。

    當前所在位置 u = 6 ,未遍歷孩子集合 v = {} ,遍歷相關查詢發現存在 LCA(T,4,6) ,且 vis[4] = true ,因此得到該查詢的解為節點 4 所在集合的首領,即 LCA(T,4,6) = 1 ,該層遞歸結束。

    遞歸返回,當前所在位置 u = 5 ,合併節點 6u 所在集合,並標記 vis[6] = true ,未遍歷孩子集合 v = {} ,無相關查詢因此該層遞歸結束。

    遞歸返回,當前所在位置 u = 1 ,合併節點 5u 所在集合,並標記 vis[5] = true ,未遍歷孩子集合 v = {} ,遍歷相關查詢發現存在 LCA(T,2,1) ,此時該查詢的解便是節點 2 所在集合的首領,即 LCA(T,2,1) = 1 ,遞歸結束。

    至此整個 Tarjan 算法便結束啦~

    PS:不要在意最終根節點的顏色和其他節點顏色有一點點小小差距,可能是在染色的時候沒仔細看,總之就這樣咯~

    PPS:所謂的首領就是、就是首領啦~

    倍增算法

    哇!還有一個倍增算法以後繼續補充吧!

    總結篇

    對於不同的 LCA 問題我們可以選擇不同的算法。

    假若一棵樹存在動態更新,此時離線算法就顯得有點力不從心了,但是在其他情況下,離線算法往往效率更高(雖然不能保證得到解的順序與輸入一致,不過我們有 sort 呀)

    總之,喜歡哪種風格的 code 是我們自己的意願咯~

    另外, LCA 和 RMQ 問題是兩個非常基礎的問題,很多複雜問題都可以轉化為這兩類問題來解決。(當然這兩類問題之間也可以相互轉化啦~)

    參考資料

    OI wiki https://oi-wiki.org/graph/lca/

    https://blog.csdn.net/my_sunshine26/article/details/72717112

    https://wizardforcel.gitbooks.io/the-art-of-programming-by-july/content/03.03.html

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

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    FB行銷專家,教你從零開始的技巧

  • 帶你學夠浪:Go語言基礎系列 – 8分鐘學複合類型

    文章每周持續更新,原創不易,「三連」讓更多人看到是對我最大的肯定。可以微信搜索公眾號「 後端技術學堂 」第一時間閱讀(一般比博客早更新一到兩篇)

    對於一般的語言使用者來說 ,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 切片

    切片是變長的序列,序列中每個元素都有相同的類型。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創建切片,指定切片長度和容量
    // 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操作

    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
    

    map 映射類型

    在 Go 中 map 是鍵值對類型,代表 keyvalue 的映射關係,一個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 遍歷

    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)
    }
    

    map 遍歷

    當使用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 排版

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

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 最新 iOS 框架整體梳理(三),最新 iOS 框架整體梳理(一),最新 iOS 框架整體梳理(二),iOS – QuartzCore

    最新 iOS 框架整體梳理(三),最新 iOS 框架整體梳理(一),最新 iOS 框架整體梳理(二),iOS – QuartzCore

     

          這一篇得把介紹框架這個系列終結了,不能超過三篇了,不然太長了….. 還是老規矩,前面兩篇的機票在下方:

          最新 iOS 框架整體梳理(一)

          最新 iOS 框架整體梳理(二)

     

    Part – 3

     

               

     

    62、Metal  MetalKit

           Metal ( [ˈmetl] )  這是一個和 OpenGLES 類似的面向底層的圖形處理接口,這也是蘋果自己搞出來的,所以這個框架我還是推薦要有一個大概的了解。

           Metal 系列教程(1)- Metal 介紹及基本使用  (系列文章三篇都是講述 Metal 的,可以學習一下)

           iOS漸變二維碼之Metal實現篇

           官方文檔

    63、MetalPerdormanceShaders

           其實這個 MetalPerdormanceShaders 也是屬於Metal的內容,關於它的具體的使用我推薦一篇利用它組高斯迷糊的文章。

           學習用MetalPerformanceShaders進行圖像處理

           官方文檔

    64、MetricKit

           這是一個在 iOS 13 中新加入的框架,iOS 13 中推出了MetricKit,它用於收集和處理電池和性能指標。

           iOS MetricsKit 收集電量和性能數據

           官方文檔

    65、MobileCoreServices

           要是在iOS10 以後在有一些APP之間跳轉的時候是需要這個框架的,我也了解了一下關於這個框架,幾乎說的都是使用它的私有API的情況下跳轉,所以不推薦使用!按照現在的審核要求私有API是行不通的,要承擔被下架的風險,具體的UTIs可以在下面查詢.

           UTIs

    66、ModelIo

          這個框架出來的相對比較早了 iOS 9 的時候發布的,但在日常中使用的還真的不多,但關於這個框架的基本的認知還是可以通過官方文檔了解到的。

          官方文檔 

    67、MultiPeerConnectivity

           這個框架我們也是有必要了解一下的,它主要是用於iOS設備間的通信,就像我們兩台iOS設備間使用 Airdrop 傳輸文件等都是屬於iOS通訊的,藉助這個機會我也給大家介紹一個直接從手機拍照導入mac的快速方法,右鍵桌面,見下圖。這個是我自己經常會用到的一個東西。

     

     

           下面是對於iOS設備間通信方式的一個總結小圖:

     

     

            圖片來源於  iOS近距離實時通信解決方案 這篇文章也能讓我們了解這個框架。

            官方文檔

    68、NaturalLanguage、

           這是一個很有趣的框架,是在iOS12中新加入的,大家在發微信消息的時候比如說了句“我想你了”微信就會有小星星雨下落,當然不一定微信是利用這個框架實現的,但這個自然語言分析框架也的確能幫我們實現這一點。具體它的使用以及怎樣分析語言的就需要我們自己探索一下了。

           Apple NLP框架NaturalLanguage的應用實例

           官方文檔

    69、NetWork  NetWorkExtension

          它可給系統WiFi列表列表裡邊的WiFi設置密碼 、標籤(副標題)。 還可獲取整個WiFi列表。獲取到WIFI列表之後呢,判斷有沒有連接上自己公司的WIFI,然後讓他打卡上班?這個我真沒試過,要有這種需求還真的是有點厲害!

         iOS 獲取系統wifi列表,wifi信號強度,並給wifi設置密碼,標籤(副標題)

         官方文檔

    70、NewsstandKit ( deprecated 

    71、NotificationCenter

          框架這東西整理的時候我發現兩個問題,最不常用的、最常用的反而是最難料理的。這個通知就是,不管是本地通知還是遠程通知我相信大家用的都很熟悉很熟悉了!所以關於它真的也只能一筆帶過了,不過還是提一句,通知框架里的東西的確需要我們掌握的,尤其是在iOS10之後蘋果在通知上是下了一份功夫的。

    72、OpenAL

          它也是一個音頻播放的框架,我們前面說過的關於音頻播放的框架真的不少了,像 AudioToolbox ,但它們之間還是有區別的,在延時、緩存等方面存在着區別。

          OpenAL的一些知識點

    73、OpenGLES

          iOS上繪製圖形的方式很多,UIKit,CoreGraphics,SpriteKit,OpenGL ES,Metal等。OpenGL ES是一套非常底層但使用非常廣泛的C語言API,專為移動設備定製,可在不同的手機系統或瀏覽器上使用,渲染效果非常好。

          iOS-OpenGLES  這是個系列文章,從這裏進去有好多的東西等着你學習呢。

    74、PassKit

          PassKit 框架在您的應用程序中請求和處理Apple Pay付款。 創建,分發和更新电子錢包應用的通行證。

          iOS PassKit Wallet 開發

          官方文檔

    75、PDFKit

           iOS 11 后蘋果在iOS平台開放了PDFKit SDK,可以使用這個框架显示和操作 pdf 文件,此項目應用PDFKit實現显示pdf、显示縮略圖、展開大綱和搜索文字的功能。這個框架還是值得我們好好學習一下的。

           iOS PDFKit框架講解

           官方文檔

    76、PencilKit

           這個框架是在iOS13中加入的,PencilKit可讓您輕鬆快捷地將手繪內容整合到iOS或macOS應用中。 PencilKit為iOS應用程序提供了一個繪圖環境,該環境可以從Apple Pencil或用戶的手指中獲取輸入,並將其轉換為您在iOS或macOS中显示的高質量圖像。該環境附帶了用於創建,擦除和選擇線條的工具。

           官方文檔

    77、Photos   PhotosUI

           這兩個框架是開發者比較熟悉常用的,它的最低適配版本是iOS 8,所以以前的相冊框架幾乎也都是不用了。關於它的資料網絡是哪個還真的不少,所以我們也就不多說了。

           官方文檔

    78、PuskKit  (很慚愧,沒找到資料)

    79、QuartzCore

           這個框架相信大家還是比較熟悉的,它裏面的內容我們在日常開發中也經常會用到,比如 CAAnimation(動畫),CADisplayLink(定時器),CAShapeLayer(圖層),CAGradientLayer(漸變)等等,一起拿我有寫文章大概的介紹過這個框架。

           iOS – QuartzCore

    80、QuickLook  QuickLookThumbnailing (Thumbnail [ˈθʌmneɪl] 縮略圖)

           QuickLook幾乎可以預覽幾乎所有的文件,像圖片、音樂,視頻、PDF、Word等都是可以。但是其可定製部分比較少,樣式比較單一,這是它的缺點。

           iOS快速預覽——QuickLook

           QuickLook官方文檔

           QuickLookThumbnailing官方文檔

    81、RealityKit

          RealityKit 是iOS 13 + 專為增強現實技術開發的一款新的高級框架,它可以處理渲染的所有方面,包括材質、陰影、反射,甚至相機的運動模糊。它還為多人AR應用程序處理網絡,這意味着開發人員不需要成為網絡工程師就可以來開發共享AR體驗,這個框架會和後面介紹的 SceneKit 和 ARKit 配合使用

          iOS ARKit,SceneKit,RealityKit總結

          官方文檔

    82、ReplayKit

          這是一個錄製屏幕的框架,但在不同的iOS版本中確有許多不同的表現,這個大家可以看下面分享的文章看一下。這一塊的需求應該也有,主要應該還是集中在遊戲中吧。

          iOS端使用replaykit錄製屏幕的技術細節

          官方文檔

    83、SafariServices

          這個框架看前面的Safari就知道和Safari瀏覽器相關了,你可以把瀏覽器集成到項目中然後瀏覽器上面能做的事你都可以做。具體的還是見官方文檔,在實際的項目中我們對這個框架的利用率感覺不是特別高。

          官方文檔

    84、SceneKit

           在前面說RealityKit框架的時候有提過這個框架,還是那句話它和RealityKit還有ARKit都是處理AR方面的內容的,你了解其中一個的時候回自然的了解到別的框架。

           官方文檔

    85、Security

          Security 框架用於保證應用程序所管理之數據的安全。該框架提供的接口可用於管理證書、公鑰、私鑰以及信任策略。它支持生成加密的安全偽隨機數。同時,它也支持對證書和Keychain密鑰進行保存,是用戶敏感數據的安全倉庫。

          關於它官方文檔最後面一個注意點說的挺明確的,內容如下:

           其實上面的大致意思就是說在iOS中我們平常使用的像URL等都是建立在安全框架基礎上的,所以我們沒必要刻意的使用這個安全框架,要視情況而定。

           官方文檔

    86、Social

           這也是一個社會化分享框架,只不過的原生的,所以在一些簡單的分享中我覺得還是可以一試的,沒必要一個不怎麼沉重的功能上一把第三方的殺牛刀。

           ios原生社交分享實踐

           官方文檔

    87、SoundAnalysis

           使用SoundAnalysis框架來分析音頻,並將其識別為特定類型,比如笑聲或掌聲。框架使用由MLSoundClassifier訓練的核心ML模型來執行分析。使用框架的能力分析流或基於文件的音頻,讓您添加智能音頻識別功能到您的應用程序。這個框架看介紹我覺得是一個很有意思的點,有空研究一下。

           官方文檔

    88、Speech

           這是一個語音識別的框架,也是很有趣的一個框架。建議大家都了解學習一下。

           iOS-Speech Framework

           官方文檔

    89、SpriteKit

           以前在接觸Cocos2d-JS的是有才有的“精靈”這個概念,你要不涉及這一塊那你知道那是一個和遊戲來發相關的框架就可以了,要是你是做遊戲的那我相信這個框架你也早都應該了解了。

           iOS SpriteKit 遊戲

           官方文檔

    90、StoreKit

           蘋果的內購相信大家也都有了解,這個框架就是專門用來處理內容的,有條件的我建議還是好好了解一下關於內購的知識。你再找它的資料的時候不塌搜索這個框架名稱,你直接搜索iOS 內購即可,這樣找打的資源相對多一些。以前有寫過關於內購的內容,有興趣的可以翻翻我以前的博客。

          官方文檔

    91、SwiftUI

          這個是一個全新的UI框架,它應該在以後也是一個趨勢,就像Swift一樣,它裏面的東西我們是有必要進行一個學習的。當然學習的資料也是相當的豐富。所以下面我們就只給出一個官方的文檔,具體的內容可以自己上網去篩選。

          官方文檔

    92、SystemConfiguration

          看網上的資源說這個框架也是一個用來測試網絡連接狀態的框架,但具體的使用又似乎不多。但的確可以嘗試,要是效果不多的話我建議能用原生的盡量避免使用第三方。

    93、Twiteer  UIKit  這兩個框架知道就行了,因為一個幾乎不用一個幾乎每天都用,的確沒有更多的可以說了。

    94、UserNotifications UserNotificationsUI

           這兩個框架在iOS10給的最大的一個驚喜,的確在10以後把通知優化的很是強大。這兩個框架相信很多人都知道,就沒必要在細說,葯還有不知道該怎麼處理的的確是應該去好好的研究一下他們。

    95、VideoSubscriberAccount

           iOS10引入了Video Subscriber Account框架(VideoSubscriberAccount.framework)來幫助應用支持流媒體認證或認證視頻點播(也被稱為TV Everywhere)與他們的有線電視或衛星電視供應商認證。 對於那些用戶註冊一次就能解鎖流媒體訂閱服務的應用來說,使用這個框架中的API可以幫助你支持單一登錄體驗。   

           這個框架的確我也沒有使用過,它是一個和AppleTV掛鈎的框架,具體的信息大家可以去看官方文檔。

           官方文檔

    96、VideoToolbox

           這個框架使讓用戶可以自行對視頻進行硬編解碼操作。關於視頻的硬編碼和解碼我也在學習計劃的當中,建議還是過一遍裏面的東西。

           iOS 利用VideoToolBox對視頻進行編解碼

           iOS利用VideoToolbox實現視頻硬解碼

           官方文檔

    97、Vision VisionKit ([ˈvɪʒn] 視力;美景;眼力;幻象)

           這個框架也是一個比較值得我們深入研究的框架,它是一個可以用來做識別圖像的框架。像面部檢測、矩陣碼/條形碼檢測等等,具體的可以在官方文檔裏面看到或者下面的文章都是可以看到的。

           iOS Vision 框架概覽

           iOS Vision的使用

           官方文檔

    98、WatchConnectivity

           這個框架看名字就能很好的理解它的作用了,它是用於 Watch 應用和 iOS 設備傳輸數據的框架。

           WatchConnectivity 介紹:告別加載等待。

           官方文檔

    99、WebKit

           這個框架也是日常中經常會用到的一個框架,WKWebView就是它裏面的Web頁面展示View,現在iOS端的網頁幾乎應該都是使用WK展示的吧,UIWebView已經被廢棄了,再用會影響到審核。這個框架具體的內容像和JS交互這個我們就不再提了,網上關於它的資料還真的不少。

     

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

    【其他文章推薦】

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

  • 算法漫遊指北(第十一篇):歸併排序算法描述、動圖演示、代碼實現、過程分析、複雜度

    算法漫遊指北(第十一篇):歸併排序算法描述、動圖演示、代碼實現、過程分析、複雜度

    一、歸併排序

    歸併排序是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為2-路歸併。

    • 所謂“分”,指的是將一個亂序數列不斷進行二分,得到許多短的序列。

    • 所謂“治”,指的是將這些短序列進行兩兩合併,然後將合併的結果作為新的序列,再與其他序列進行合併,最終得到一個新的序列。

    歸併排序算法描述

     

    • 把長度為n的輸入序列分成兩個長度為n/2的子序列;

    • 對這兩個子序列分別採用歸併排序;

    • 將兩個排序好的子序列合併成一個最終的排序序列。

     

    歸併排序動圖演示

     

    動畫演示圖2

     

     

    歸併排序代碼實現

    def merge_sort(alist):
        """歸併排序"""
        n = len(alist)
        #遞歸結束條件
        # 剩一個或沒有直接返回,不用排序
        if n <= 1:
            return alist
        # 拆分
        mid = n//2
    ​
        # left 採用歸併排序后形成的有序的新的列表
        left_li = merge_sort(alist[:mid])
    ​
        # right 採用歸併排序后形成的有序的新的列表
        right_li = merge_sort(alist[mid:])
    ​
        # 將兩個有序的子序列合併為一個新的整體
        # merge(left, right)
        left_pointer, right_pointer = 0, 0
        result = []
    ​
        while left_pointer < len(left_li) and right_pointer < len(right_li):
            if left_li[left_pointer] <=  right_li[right_pointer]:
                result.append(left_li[left_pointer])
                left_pointer += 1
            else:
                result.append(right_li[right_pointer])
                right_pointer += 1
        
        #將兩個列表按順序融合為一個列表result
        result += left_li[left_pointer:]
        result += right_li[right_pointer:]
        return result
    ​
    

      


    歸併排序過程分析

    示例 針對 arrli = [6,5,3,1,8,7,2,4]進行歸併排序

    1、拆分數組

    假設數組一共有 n 個元素,我們遞歸對數組進行折半拆分即n//2,直到每組只有一個元素為止。

     

    2、合併數組

    算法會從最小數組開始有序合併,這樣合併出來的數組一直是有序的,所以合併兩個有序數組是歸併算法的核心,這裏用兩個簡單數組示例:

    步驟1:新建一個空數組存放合併結果,用left_pointerright_pointer兩個輔助指針記錄兩個數組當前操作位置;

     

    步驟2:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到left_pointerright_pointer指針超出尾部;

     

    步驟3:新建一個空數組存放合併結果,用lr兩個輔助指針記錄兩個數組當前操作位置;

     

    步驟4:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到lr指針超出尾部;

     

    繼續比較寫入較小的元素到新數組

    繼續比較寫入較小的元素到新數組

     

    指針尚未移到尾部的數組,說明還有剩餘元素,將剩餘元素合併到新數組尾部。

     

    步驟5:新建一個空數組存放合併結果,用lr兩個輔助指針記錄兩個數組當前操作位置;

     

    步驟6:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到lr指針超出尾部;

     

    將較小的元素寫入到新數組

    繼續比較寫入較小的元素到新數組

    繼續比較寫入較小的元素到新數組

    繼續比較寫入較小的元素到新數組

     

    步驟7:右邊的指針尚未移到尾部的數組,說明還有剩餘元素,將剩餘元素合併到新數組尾部。

     

    完成歸併排序,返回排好序的新數組

     

     

    歸併排序複雜度

     

    • 時間複雜度:O(nlogn)

    歸併排序把數組一層層折半分組,長度為 n 的數組,折半層數就是 logn,每一層進行操作的運算量是 n,得出時間複雜度 O(nlogn)。

    • 空間複雜度:O(n)

    每次歸併操作需要創建額外的新數組,佔用空間為 n,但這部分額外空間會隨着方法的結束而釋放,所以只需要算單次歸併操作開闢的空間即可,得出空間複雜度 O(n)。

    • 穩定性:穩定

    從算法中從左到右逐一比較,較小的先放入新數組,所以兩個值相同的元素,排序后依然保持原先後順序。

     

     

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

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

  • 這一次搞懂SpringMVC原理

    這一次搞懂SpringMVC原理

    @

    目錄

    • 前言
    • 正文
      • 請求入口
      • 組件初始化
      • 調用Controller
      • 參數、返回值解析
    • 總結

    前言

    前面幾篇文章,學習了Spring IOC、Bean實例化過程、AOP、事務的源碼和設計思想,了解了Spring的整體運行流程,但如果是web開發,那麼必不可少的還有Spring MVC,本篇主要分析在請求調用過程中SpringMVC的實現原理,通過本篇要搞懂它是怎麼解決請求、參數、返回值映射等問題的。

    正文

    請求入口

    我們都知道前端調用後端接口時,都會通過Servlet進行轉發,而Servlet的聲明周期包含下面四個階段:

    • 實例化(new)
    • 初始化(init)
    • 執行(service調用doGet/doPost)
    • 銷毀(destroy)

    前兩個階段在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);
    		}
    	}
    

    可以看到最終是調用WebMvcConfigureraddInterceptors方法,也就是我們對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組件的初始化邏輯,其它組件實現邏輯相同,下面不再分析。

    調用Controller

    回到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並返回。
    但這裏你可能會比較好奇urlLookupmappingLookup從哪裡來的,仔細觀察你會發現當前這個類實現了一個接口InitializingBean,實現了這個接口的類會在該類的Bean實例化完成后調用afterPropertiesSet方法,上面的映射關係就是在這個方法中做的。實際上這個方法不止完成了上面兩個映射關係,還有下面兩個:

    • corsLookup:handlerMethod -> corsConfig
    • registry:RequestMappingInfo -> MappingRegistration(包含url、handlerMethod、RequestMappingInfo、name等信息)

    這裏就不展開分析了,奉上一張時序圖,讀者可根據下面的時序圖自行分析:

    拿到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;
    		}
    	}
    

    這個方法裏面主要看invokeForRequesthandleReturnValue的調用,前者是完成參數綁定並調用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這個龐大的體系來說,還有很多是要去理解學習的,尤其是設計思想,只有長期琢磨才能深刻的理解掌握。在我之前的文章中包括本篇還有很多沒分析到的細節,在後面我會不定期分享出來。

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

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 工欲善其事,必先利其器 — Mac 軟件推薦(序)

    工欲善其事,必先利其器 — Mac 軟件推薦(序)

    背景

    工欲善其事,必先利其器。​後面我將陸陸續續推薦一些軟件利器幫助大家提高效率(主要針對 Mac 電腦)。

    如果你在使用 Mac 電腦,並且沒有如某些人那樣安裝並使用 Windows 系統,那麼你可以嘗試使用以下這些軟件。

    在 Mac 裝 Windows 使用,感覺有點“暴殄天物”(文化有限,只能找到這個詞),沒有惡意黑 Windows,Windows 有 Windows 的使用場景,對於普通人民群眾來說,確實使用 Windows 夠了,微軟現在也出了不錯的筆記本。但你確實不該買 Mac 然後確使用 Windows 系統,這樣其實裝 X 效果不好。

    這些軟件都是我自己使用過且覺得還不錯的,這些軟件或者可以極大地提高效率或者偶爾也足夠裝13(哈哈,亂入了一兩款 App)。

    整理下來太多了,因為太多圖,放在一篇文章裏面感覺加載都有點問題(是不是暗示我要換手機了?)。正好有讀者反饋說之前發的有的內容太長太干,都看不下去了,因此,我進行了拆分(技術乾貨花的時間也久,產出沒那麼快)。正好用類似的文章休息下,不用動腦筋,1~2分鐘搞定,並且也有收穫,​一舉兩得。​

    主角登場 Alfred

    今天的主角是 Alfred。這個軟件很多文章都在說,我這裏就不多做過多介紹了。其具體效果跟 Mac 自帶的 Spotlight 類似,但功能會強大 N 個數量級倍。

    我差不多 12 年開始接觸 Mac,當時還是窮學生,托香港的同學幫忙買的教育版 MacBook Air,現在還偶爾服役。但使用這款軟件是我 15 年快工作了才用上,後悔沒早知道呀,不過現在也已經陪伴我走了這麼多年了,首推就是這款軟件了。如果你看到這篇文章且還沒有用過,就趕緊用起來吧,免費版本的功能也都已經挺強悍了。

    舉例說下常用的幾個功能:

    文件搜索

    類似 Windows 版本的 everything。 設置某個標識(示例中為 “’”)開頭,後面為關鍵字就開始全盤索引(當然可以配置過濾)了,找到搜索到的文件后,按 “->” 出現二級菜單,可以選擇下一步的操作。

    比如複製,以此命令行 cd 到文件/目錄(後面有類似的工具推薦),複製文件路徑(finder 不比 windows 能夠方便 copy 文件路徑)等。

    alfred-file-search

    剪貼板歷史

    可以幫你保存你最近的剪貼板歷史,通過快捷鍵選取粘貼。實際工作中經常遇到,本來要複製一個東西已經 cmd+c 了,這個時候又來一個更優先需要複製粘貼的,前面那個又被覆蓋了,還得再去複製一遍。有了這個功能就不愁了。

    alfred-paste

    各種搜索

    • 搜索引擎搜索

    同樣可以設置關鍵字,比如 “google keywords”,回車就能直接打開 google 搜索。默認的有google/wiki/等等,這個還可以自己方便添加更多的搜索引擎,比如 baidu,必應,stackoverflow 等等。

    • 各種快捷搜索

    其他的比如聯繫人搜索,快捷功能(lock/sleep/shutdown)等等,計算器(直接輸入等式即可),輸入應用名稱快速打開應用等等。

    alfred-quick-search

    Workflow

    Workflow 是其更強大的賣點。比如以下是一些或者極其高效或者很有意思的 workflow。

    • Dash

    堪稱程序員神器啊。 結合 Dash,能夠非常方便快捷地搜索某種語言的某個 API,再也不用邊寫邊打開瀏覽器去搜索了。

    遇到了 某個 API 不太清楚,直接 ctrl + blank 輸入關鍵字就直接模糊搜索某 API 了。

    alfred-dash

    • stackoverflow

    其實這個通過在上面的搜索引起那裡設置也 OK 的。這裡是一個單獨的 workflow,同樣可以設置關鍵字(例如 st keywords) 就能直接搜索 stackoverflow 上相關問題。相當於在 google 搜索中 keywords site:stackoverflow.com

    alfred-stackoverflow

    • youdao 翻譯

    遇到中英文翻譯問題不用再打開瀏覽器去搜索了。

    當然 Mac 自帶的取詞翻譯功能也挺不錯的: 不知道? 選中關鍵字,三指輕點觸控板。

    mac-translate

    • zhihu

    知乎搜索及知乎日報,可以設置關鍵字直接知乎搜索,或者列出當天的知乎日報推薦列表。

    • douban

    豆瓣的相關功能,豆瓣讀書/電影等。最近聽到同事談論某電影,想看豆瓣評分多少? 很簡答, 直接 movie 電影名 就出來結果了,如圖:

    • 天氣

    調用百度的 API 實現的快捷天氣預報

    alfred-weather

    • mail

    快速搜索郵件(這裏直接用的以前的截圖)。

    alfred-mail

    • 印象筆記(evernote)

    快速搜索印象筆記/evernote 中保存的內容。這個得首先去 印象筆記官網 生成一個 token,然後安裝好 alfred-evernote后,配置好(es-token 你自己的generated-token) token 成功后就可以使用了。

    查詢有不同的語法格式,詳情可以查閱evernote 搜索語法。

    alfred-印象筆記 workflow

    搜索后直接回車打開是默認在應用程序中打開,按住 cmd 後會在瀏覽器中打開(由於最開始開發的作者是國際版 evernote,中國版補丁的作者也忘記改這個鏈接了,所以在瀏覽器中打開的跳轉鏈接不對,直接下載我修改后 workflow 是 OK 的 github),其實就是修改一下其中的 app.js中的 get-link 方法。

    當然還有更多其他好玩有用的 workflow,你可以直接到github AlfredWorkflow“選購”,沒有的也可以自己實現一個也貢獻出來哦。方法也相對比較簡單,用 php/python 等都可以實現,你打開 alfred 設置項,雙擊具體某個 workflow 就能看到源碼。

     

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

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    FB行銷專家,教你從零開始的技巧

  • 2019 太陽能五大趨勢:市場走向穩定與分散,度電成本將成為供應鏈價格依歸

    2019 太陽能五大趨勢:市場走向穩定與分散,度電成本將成為供應鏈價格依歸

    2018 年可說是太陽能產業近年來波動最大的一年,歷經美國 201、301 條款,中國 531 新政,印度防衛性關稅,歐盟 MIP 結束等變動,從最上游的供應鏈到最下游的系統端都呈現極不穩定的狀態。由 EnergyTrend 所盤整的 2019 年五大趨勢來看,市況將會好轉,且產業也將在持續的變動中逐漸成熟。

    趨勢一:2018 年低谷不低,2019 需求再創新高

    中國的「531 新政」雖對市場造成衝擊,但因海外市場的需求走強,加上中國市場所受衝擊輕於預期,使 2018 年出現「低谷不低」的現象,預期全年新增併網量可達到 103GW(實際出貨量約95GW),年增4.9%。

    展望 2019 年,在政策鼓勵與供應鏈價格持續下降的推波助瀾下,全球需求預計將繼續正成長,其中又以歐洲的成長幅度最大,最多可超過五成。2019 年預期新增併網量將來到 111.3GW,出現 7.7% 的成長,再次創下歷史新高。

    趨勢二:市場持續分散,2019 年 GW 級市場增至 15 

    全球市場規模自 2018 年起預計會持穩在 100-120GW 之間,各年度需求量變化幅度將低於 10%。而根據 EnergyTrend 的最新需求報告統計,GW 級市場從 2016  年的 6  個成長到 2019 年將有 15 個,可見市場持續分散化的趨勢。

    2016-2020 年 GW 級市場

    中國、美國將持續穩居全球前二大市場,印度則從 2017 年起成為第三大需求國,日本次之。東南亞、北非、中東、拉丁美洲等新興市場自 2018 年崛起,如中東地區 2018 年全年需求預計將較 2017 年增加近 100%,2019  年還將增加 50% 左右。全球市場規模自 2019 年起將趨於穩定,印度最有可能出現較大幅度的需求成長。

    2016-2023 年全球市場需求趨勢

    趨勢三:供應鏈上游更為集中,單晶將逆轉市佔

    雖然供應鏈整體在 2018 年陷於供過於求、低利潤的困境,但技術和成本優勢較強、全球布局較廣的一線大廠仍保有強勁的營運動能,既有的擴產計畫多能持續進行,使供應鏈廠家有持續集中化的現象。根據 EnergyTrend  的供給資料庫,中國前五大多晶矽廠的新產能預計在 2Q19 陸續開出,屆時前五大廠的產能將佔全球近 70%,且現金成本更具競爭力。在矽晶圓環節,則將呈現隆基與中環雙龍頭主宰市場的現象,單晶供應鏈也將因而變得更具主導性,有機會拉升全年單晶佔比來到 6 成,2017 年底展開的單多晶之戰逐漸落幕。

    趨勢四:雙面產品產能倍增,P-PERC 效率還有成長空間

    雙面電池技術已十分成熟,且可在幾乎不增加額外成本的前提下創造額外的發電收益,因此產能比例持續上升,預計 2019 年雙面電池的總產能將接近 40GW,且以雙面單晶 PERC 電池產能增加最多。另一方面,單晶 PERC 電池的量產效率仍有成長空間。據 EnergyTrend 調查,單晶 PERC 電池的平均量產效率在 2019 年上半年即可站上 22%,且還可導入更多技術,在 2019 年底效率可望上看 23%。而單晶 PERC 的強勢也壓縮了次世代 N 型技術的發展空間,2019 年 N 型產能預期僅會有小幅增加。

    雙面電池產能成長趨勢(Unit: GW)

    趨勢五:均化度電成本成為模組價格降價指標

    供應鏈價格持續下探,使太陽能逐步朝擺脫補貼、平價上網的方向邁進;而無補貼系統的普及程度及其實際的均化度電成本(LCOE)將成為未來供應鏈的價格指標。

    太陽能產業在 2018 年面臨強大考驗,但同時也進入產業盤整階段,預期長期發展將趨於穩定化與健康化,供應鏈的價格將以整體系統的度電成本為依歸。儲能系統與智慧電網技術的投入,將成為太陽能產業進一步市場化的關鍵。

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 川普視察野火災區 不認與氣候變遷有關

    摘錄自2018年11月18日TVBS新聞網美國加州報導

    美國加州爆發史上最嚴重的坎普野火,不但燒毀整座城鎮,還一路延燒了6萬公頃的面積,將近1300人失蹤,71人死亡。總統川普先前曾批評會發生野火,是因為森林管理不當,17日他也搭機來到加州視察,承諾會盡快解決野火災情,但他還是不願承認,這些跟氣候變遷有關。

    美國總統川普17日抵達加州,在當地首長陪同下,來到了幾乎已成焦土的天堂鎮視察。加州爆發史上最嚴重的坎普野火,延燒面積已經來到6萬公頃。川普先前曾批評森林管理不當,造成大火,這回他親眼見到災後現場,還是不承認氣候變遷扮演關鍵角色。

    美國總統川普:「不,我對這點(氣候變遷)很有意見,我想要好的氣候,我們會迎接好氣候,也將有非常安全的森林,因為我們不能每年都經歷(林火)。」

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 日本26年首見豬瘟案例 疑遭進口豬肉感染

    摘錄自2018年9月9日蘋果日報日本報導

    日本農業部門今(9)日證實,當地出現26年來首起豬瘟感染病例,懷疑是進口豬肉或野豬肉帶入病毒導致感染。

    發現疫情的是一間位於岐阜市的養豬場,3日到8日發現有80頭豬死亡。檢驗後證實,這些豬隻感染豬瘟,此病毒與最近在中國爆發的非洲豬瘟並不相同,也不會傳染給人類。

    根據日本家畜傳染病預防法規定,只要養豬場發現有豬隻感染豬瘟,就必須撲殺養豬場內所有豬隻。岐阜縣政府今日上午開始撲殺養豬場內的610頭豬隻,作業將持續到明天上午6時。由於豬瘟在豬隻間具傳染性,岐阜縣為預防豬瘟疫情擴大,將這間養豬場半徑10公里內劃為「禁止搬出區域」;並禁止這個區域內的其他3間養豬場出貨。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!