分類: 3C資訊

  • 地球發燒了研究:2080年前熱死人數激增20倍

    摘錄自2018年8月1日蘋果日報台北報導

    天氣讓人熱得受不了,而這種情況若不加以控制,地球上熱死人的數目將增加20倍。最新研究指出,在各國政府完全不做任何事的最糟情況,部分國家在2080年前,因為高溫導致的死亡數字將增加2000%。

    這項刊登在「公共科學圖書館醫學期刊」(PLOS Medicine)的報告指出,歐洲、部分亞洲與北美洲地區的氣溫逐年升高,熱浪造成數以千計的死亡案例。該報告首席研究員、澳洲蒙納士大學(Monash University)副教授郭玉明(Yuming Guo,音譯)說:「未來的熱浪會發生得更頻繁、更強烈,維持時間也更長」、「如果我們找不到減緩氣候變遷的方法,並幫助人民適應熱浪氣候,和熱浪有關的死亡案例將會急遽增加。」

    報告中指出,最糟的情況下,哥倫比亞在2031至2080年間被高溫熱死的人數,會比1971年至2010年增加2000%,即增加20倍;菲律賓在2031到2080因熱浪而額外死亡的人數,是1971到2020的12倍;澳洲、美國是5倍,英國是4倍。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 效率思維模式與Zombie Scrum

    效率思維模式與Zombie Scrum

    Scrum是由Ken Schwaber和Jeff Sutherland在20世紀90年代提出的概念,並在1995年首次正式確定。起初Scrum是為了解決產品和軟件開發固有的複雜性,然而現在Scrum被成功地應用於市場營銷、組織變革和科學研究等多個領域的複雜問題。

    Scrum主要建立在以下三個原則的基礎上:

    • 透明度:你需要收集數據(比如一些指標、團隊成員的反饋或其他團隊的經驗之談),從而找到你的目標。
    • 檢查:你需要和大家一起監督迭代的進度,並決定迭代完成的標準是什麼。
    • 適應:你需要做出改變,希望能更好更快地完成你的目標。

    在實施Scrum之前首先要用一段時間來定義和調整這些規則,以發現工作中的問題,找到可以改善的方向,這裏說的問題不是那種一年一次或項目完成時才發生的問題,而是每天、每周或每月都在持續發生的問題。我們不是將我們的決策建立在對可能永遠不會發生的潛在風險的假設上,而是根據我們收集到的數據來做決策,這就是所謂的經驗主義。

    Scrum的價值?

    當你需要接受你並不了解和無法控制一切的時候,Scrum提供的經驗方法就會變得非常有用。也正因如此,你會改變之前的想法,雖然可能會犯錯,但也會有新的、有價值的想法出現,而這些是你從未考慮過的。與其在前期制定一個精確的計劃,然後無論如何都要堅持下去,不如把你的想法當作假設或假說,用Scrum的方式來驗證。

    Scrum可以讓你快速了解自己是否偏離了軌道,是否需要做出調整,而不是簡單地按照計劃行事,你可以先解決你目前面臨的最大風險。當你在一個不確定的、不斷變化的環境中工作時,這一點尤為重要。你一開始的假設在當時可能是絕對正確的,但是當你在開發產品的時候,環境可能會發生很大的變化,以至於你的整個方案都失敗。在一個漫長的項目結束的時候,經驗主義的方法並不是災難性的失敗,而是將其降低為一個小的減速帶,需要你修正一下方向。

    所以,實際上Scrum是降低了複雜的、適應性問題、固有的不可預測性和不確定性的風險。它允許你不斷地驗證你仍然在做正確的事情,並朝着解決你設定的目標前進。更好的是,你現在有了一個积極發現更好想法的過程,並將其納入到下一步的塑造中。現在,不確定性反而變成了一件好事,因為其中蘊含着所有的可能性。

    “Scrum降低了複雜的、適應性問題固有的不可預測性和不確定性的風險。”

    Zombie Scrum和效率思維模式

    那麼,Zombie Scrum與這一切有什麼聯繫呢?我們發現一個現象:人們使用Scrum的起因很多都是錯誤的。當你問一個Zombie Scrum組織中的人,他們希望從Scrum中得到什麼時,你會聽到諸如 “更快”、”更多的大腦”、”更多的產出 “和 “更高的效率”。這與 “敏捷 “這個詞的實際含義是非常不同的。這與Scrum的設計目的也大相徑庭。這種矛盾從何而來?

    傳統的組織管理和產品開發方式是為了實現與敏捷性相反的目標,這種心理模式通常被稱為 “效率思維模式”。它的目的是盡可能地減少不確定性,提高可預測性,推動效率的提高。這通常表現為會制定詳細的計劃,通過協議和程序使工作標準化,高度的任務專業化,以及衡量效率(如每天的工作量、出現的問題) 。這種思維模式當然可以在工作相當重複和簡單的環境中發揮作用,比如流水線化的工作或某些行政工作,但在人們處理複雜的、適應性強的問題的環境中肯定行不通,因為這些問題本身就具有不可預測性和不確定性。

    “效率思維模式的目的是盡可能地減少不確定性,提高可預測性,推動效率的提高。”

    Zombie Scrum與領導強烈關注績效和工作量是有很大關係的,但最終客戶是否滿意?是否交付了有價值的東西?卻無人問津。而且,這種思維模式在很多企業中是根深蒂固的,它已經成為一個我們不需要討論的 “真相”。這樣的企業是想試圖用Scrum來影響效率、速度和產出的角度來理解它是有道理的,只不過當發現Scrum似乎並沒有做到這一點時,人們就會感到失望。

    從非常廣泛的意義上來說,Scrum關注的更多是效率,而不是高效。效率是為了盡可能多的完成工作(產出),而高效則是為了工作的價值和有用性(結果)。雖然完全有可能通過Scrum提高效率,但這既不是承諾也不是目標。

    在充斥着 “Zombie Scrum”的環境中,大家是很看重“效率思維”的,以至於人們只看到Scrum的結構性元素:角色、事件和工件。他們沒有看到也沒有體會到這個過程的價值。這就是為什麼Zombie Scrum只是看起來像Scrum,但沒有其精髓。

    “Scrum更關注的是有效(結果),而不是高效(產出)。”

    在這篇文章中,我們提到了Scrum的三個原則,如何在必要的時候重複進行,以捕捉工作中出現的偏差、意外發現和潛在機會。Scrum中的所有內容都是圍繞這三個支柱設計的。這也是經驗主義發揮作用的原因。採用Zombie Scrum的組織,往往有一種效率思維,目標是盡可能減少不確定性,提高可預測性,推動效率。這與在複雜工作中學習和發現的經驗主義過程相矛盾。

    原文作者:Barry Overeem

    翻譯整理:Worktile

    Worktile 官網:worktile.com

    文章首發於「Worktile官方博客」,轉載請註明出處。

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

    【其他文章推薦】

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

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

    ※台北網頁設計公司全省服務真心推薦

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

    ※推薦評價好的iphone維修中心

  • K8S-磁盤配額管理-整理

    K8S-磁盤配額管理-整理

    1.  ephemeral-storage介紹

    Kubernetes在1.8的版本中引入了一種類似於CPU,RAM的新的資源模式:ephemeral-storage屬性(直譯為臨時存儲),並且在1.10的版本kubelet默認啟用了這個特性。

    ephemeral-storage實現了對Pod應用存儲資源的管理,可以有效的降低Pod應用失控消耗完 node磁盤空間的風險。官網中對該屬性的描述如下:

    (https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/):

     

    從上述官網介紹,總結如下:

    1. 臨時存儲:臨時的含義是指容器內的數據未做持久化處理,生命周期和容器一致。
    2. 作用對象:容器日誌(/var/log)、EmptyDir類型的volume數據(/var/lib/kubelet)、鏡像層和容器可寫層(/var/lib/docker)。由此可見,基本覆蓋了Pod各個方面的磁盤消耗。
    3. 管理的文件系統:ephemeral-storage對kubelet的根目錄(默認是/var/lib/kubelet)所在的節點分區(文件系統)進行管理,即如果把/var/lib/docker獨立分區,ephemeral-storage將不對/var/lib/docker目錄進行管理。
    4. Pod調度流程:節點上的kubelet啟動的時候,kubelet會統計當前節點的kubelet分區的可分配的磁盤資源,或者你可以覆蓋節點上kubelet的配置來自定義可分配的資源。在創建Pod時會根據存儲需求調度到滿足存儲的節點,在Pod使用超過限制的存儲時會對其做驅逐的處理來保證不會耗盡節點上的磁盤空間。

    2. ephemeral-storage功能驗證

    2.1   環境準備

    • 虛擬機配置

    1)    規格:16 vCpu + 80 GB RAM + 1000 GB 磁盤

    2)    分區:/var/lib/docker、/var/lib/kubelet/和/var/log全在同一個系統分區上

    • 測試容器鏡像

    1)  Dockerfile

    FROM ubuntu:16.04
    ADD start.sh /home/
    RUN mkdir -p /lq/log/
    ENTRYPOINT /home/start.sh

    2)  Start.sh

    #!/bin/bash
    while true
    do
    dateString=`date`
    echo "$dateString==================================">> /lq/log/test.log
    done
    • 集群環境

    1)  Kuberneters版本:1.15.6

    2)  Docker版本:18.06

    2.2   容器可寫層大小

    • 容器的部署文件

     

    說明:

    1)    容器的啟動腳本start.sh會持續的向容器內路徑/lq/log下寫test.log日誌

    2)    該日誌並未掛載出來,故日誌文件在宿主機的容器可寫層目錄下

    3)    該容器申請10Mi的磁盤空間,上限為20Mi

    • 創建該Pod

    使用kubectl apply -f xxxx.yaml,觀察可寫層日誌大小情況以及Pod運行情況

    很快可寫層的日誌就達到了16Mi

    • 繼續觀察Pod

     

    Pod驅逐了(容器被殺掉,容器內數據全部丟失)

     從上述Event事件可以看到,Pod可用磁盤空間被限制住了

    2.3   EmptyDir日誌

    • 部署文件

     

    說明:

    1)    容器的啟動腳本start.sh會持續的向容器內路徑/lq/log下寫test.log日誌

    2)    該日誌通過EmptyDir掛載出來,故日誌文件在宿主機的/var/lib/kubelet目錄下

    3)    該容器申請10Mi的磁盤空間,上限為100Mi,emptyDir路徑上限為40Mi

    • 創建該Pod

    使用kubectl apply -f  xxxxxxxx.yaml

    • 查詢日誌路徑以及Pod運行情況

     

     

     

    可以看到日誌已經到了32Mi,目前Pod運行正常

    • 繼續等待,觀察Pod情況

     

    Pod被驅逐了(容器殺死,全部數據丟失)

    • 查看Pod事件

     

    可見該日誌磁盤空間被限制了

    2.4   /var/log目錄日誌

    一般Pod的啟動日誌(k8s上的控制台日誌)會記錄到宿主機的/var/log目錄下,並且根據前面介紹得知ephemeral-storage會對該目錄下的容器日誌磁盤空間大小進行管理,但是由於我使用的測試鏡像並無啟動日誌,故通過hostPath掛載的方式掛載到該路徑下,看看我們显示指定掛載路徑的時候,ephemeral-storage還能否生效。

    • 部署文件

     

    • 創建該Pod

     

    • 觀察Pod運行情況

     

    可以看到,自己显示掛載的路徑並做不到磁盤空間限制。

    • 換一種思路

    由於無啟動日誌,故想書寫腳本向容器的啟動日誌瘋狂輸出日誌,觀測Pod運行情況

    1)  修改start.sh

     

    2)  修改部署文件

     

    3)  創建該Pod

     

    Docker logs查看,容器在瘋狂的輸出日誌

     

    4)  持續一段時間,觀察Pod運行狀態

     

    Pod驅逐了

    2.5   其他情況說明

    1. /var/lib/docker和/var/lib/docker分區文件系統不一致。未做截圖,但是已經實際驗證:/var/lib/docker的分區和kubelet分區不一致的時候,ephemeral-storage對/var/lib/docker目錄磁盤空間大小不做管控

    3.  驗證結論

    1. 分區要一致,否則ephemeral-storage管理不到
    2. ephemeral-storage管理的是容器相關的目錄路徑下的磁盤大小,自己顯式掛載的定製化路徑無法控制磁盤空間

    4.  參考文檔

    1. https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
    2. https://blog.csdn.net/sdmei/article/details/101017405(總結的非常好)

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※回頭車貨運收費標準

  • 圖像處理中的valid卷積與same卷積

    valid卷積

    在full卷積的卷積過程中,會遇到\(K_{flip}\)靠近I的邊界(K矩陣與I矩陣),就會有部分延申到I之外,這時候忽略邊界,只考慮I完全覆蓋\(K_{flip}\)內的值情況,這個的過程就是valid卷積。一個高為H1,寬為W1的矩陣I與高為H2,寬為W2的矩陣K,在H1大於等於H2,W1大於等於W2的情況下,valid卷積的結果就是一個(H1-H2+1)*(W-W+1)的矩陣\(C_{valid}\)

    \[C_{valid}與C_{full}的對應關係為: C_{valid} = C_{full}( Rect (W_{2}-1,H_{2}-1,W_{1}-W_{2}+1,H_{1}-H_{2}+1) ) \]

    same卷積

    無論是full卷積還是valid卷積都不會得到正好的尺寸,要麼比原尺寸大要麼比原尺寸小,這時就需要same卷積來解決這個問題。若想得到寬和高都正好的矩陣我們首先需要給\(K_{flip}\)一個錨點,將錨點放在(循環)圖像矩陣的(r,c)處,((r,c)在矩陣之內),將對應位置的元素逐個相乘,最終將所有的積進行求和作為輸出圖像矩陣在(r,c)處的輸出值。這個過程稱為same卷積。
    OpenCv函數copyMakeBorder的參數表

    參數 解釋
    src 輸入矩陣
    dst 輸出矩陣
    top 上側擴充的行數
    bottom 下側擴充的行數
    left 左側擴充的行數
    right 右側擴充的行數
    borderType 邊界擴充的類型
    value border Type= BORDER_CONSTANT事的常數

    其中borderType有多種類型,比如:BORDER_REPLICATE(邊界複製)、BORDER_CONSTANT(常數擴充)、BORDER_REFLECT(反射擴充)等。
    在使用Python進行卷積操作時用到包Scipy,其中有關的操作函數為convolve2d(in1,in2,mode=’full’,boundary=’fill’,fillvalue=0)

    參數 解釋
    in1 輸入數組
    in2 輸入數組,代表K(卷積算子)
    mode 卷積類型,也就是以上提到的三種類型:full,valid,same
    boundary 邊界填充:fill\wrap\symm
    fillvalue 當boundary=’fill’時,設置邊界填充的值,默認為0

    在這裏需要注意的是當model為same時卷積算子的錨點位置由不同尺寸而不同,假設K(卷積算子)的寬和高分別為W、H。

    W和H的值 錨點位置
    均為奇數 默認為中心點
    H為偶數、W為奇數 (H-1,(W-1)/2)
    H為奇數,W為偶數 ((H-1)/2,W-1)
    均為偶數 (H-1,W-1)

    代碼實現:

    import numpy as np
    from scipy import signal
    
    if __name__ == "__main__":
    
        I = np.array([[1,2],[3,4],np.float32])
        #I的高和寬
        H1,W1 = I.shape[:2]
        #卷積算子
        k = np.array([[-1,-2],[2,1],np.float32])
        #K的寬和高
        H2,W2 = k.shape[:2]
        #計算full卷積
        c_full = signal.convolve2d(I,k,mode='full')
        #設定錨點
        r,c = 0,0
        #根據錨點來從full卷積中截取same卷積
        c_same= c_full[H2-r-1:H1-r-1,W2-c-1:W1+W2-c-1]
    

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

    【其他文章推薦】

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

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

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

    ※超省錢租車方案

  • 編碼的道與禪

    該文章發布在github中,如果您覺得寫的還不錯的話,可以 star 一下進行支持,傳送門:TechShare。

    Bob 大叔在《代碼整潔之道》一書的前言打趣着說,當你寫的代碼在經受代碼審查時,如果審查者憤怒的吼道“What the fuck is this shit?”等言辭激烈的詞語時,那說明你寫的是 Bad Code;如果審查者只是漫不經心的吐出幾個“What the fuck?”,那說明你寫的是 Good Code。這就是衡量代碼質量的唯一標準——每分鐘罵出“What the fuck?”的頻率。

    想寫出整潔的代碼很難,有一部分原因在於糟糕的代碼太容易編寫。想快點完成任務時,考慮不周全時,忽略安全時,隨意命名時,參數過多時,嵌套太深時,未及時更改註釋時,違反法則時,重複你自己時等等情形,我們有太多的機會來製造糟糕的代碼。只有嚴肅對待自己的代碼,了解哪些事情會使我們的代碼變味,才有可能寫出整潔的代碼。

    寫代碼和寫文章在某種程度上有相似之處,好的文章一定有好的可讀性,寫代碼也一樣,只有優美乾淨的代碼才能具有良好的可讀性。編寫具有可讀性的代碼不光是保持有意義的命名就行,如果你想成為一名更好的程序員,寫代碼時你需要注意的有很多,比如:

    1. 規範本地變量的位置
    2. 使函數盡量短小
    3. 調用者盡可能放在被調用者上面
    4. 保持代碼擁有良好的格式
    5. 編寫只做一件事的函數
    6. 函數參數不要超過三個
    7. 暴露時序耦合
    8. 使用異常代替返回錯誤碼

    除此之外,你還須牢記眾多設計原則,如:

    1. 開放封閉原則(OCP)
    2. 迪米特法則
    3. 依賴倒置原則(DIP)
    4. 單一職責原則(SRP)
    5. 里氏替換原則(LSP)
    6. 不要重複(DRY)
    7. 你不會需要它(YAGNI)

    當然僅有這些是不夠的,這不是騎自行車,學寫整潔代碼得花許多功夫,必須不斷實踐,從失敗中提取代碼的壞味道並從中得到啟發。

    編寫整潔代碼,你需要牢記並遵守很多東西,但這並不是循規蹈矩和刻板,而是對簡單之美、代碼之美的追求。代碼整潔之道,是編寫優秀代碼的一種方法,其核心是儘力使代碼保持簡單——Keep It Simple, Stupid。判斷一個人寫的代碼的好壞,不是看它的代碼寫的有多複雜,而是看他有沒有把複雜的事物抽象出來並用簡單的方式去描述它,此外這個人對代碼的態度也至關重要,大多數時候我們並不能從一開始就把代碼寫的很完美,當我們需要快速做出一個原型,或者一開始代碼看起來不錯,但新的需求使現有的設計無法滿足,如果不對設計進行改動的話,那麼代碼就會變的醜陋,如果你熱愛自己正在做的事情,崇尚代碼之美,那麼你就會有足夠的動力去重構它、完善它,而不是破壞結構使代碼腐爛。

    保持簡單、追求簡單,我想這就是編碼之中的禪意,一種追求本真的境界。這種禪在 Python 的設計哲學中體現的淋漓盡致,讓我們在 Python 解釋器中輸入“import this”,來看看經典的 Python 之禪。

    • Beautiful is better than ugly.
      優美勝於醜陋。
    • Explicit is better than implicit.
      顯式勝於隱式。
    • Simple is better than complex.
      簡單勝於複雜。
    • Complex is better than complicated.
      複雜勝於難懂。
    • Flat is better than nested.
      扁平勝於嵌套。
    • Sparse is better than dense.
      分散勝於密集。
    • Readability counts.
      可讀性應當被重視。
    • Special cases aren’t special enough to break the rules. Although practicality beats purity.
      儘管實用性會打敗純粹性,特例也不能凌駕於規則之上。
    • Errors should never pass silently. Unless explicitly silenced.
      除非明確地使其沉默,錯誤永遠不應該默默地溜走。
    • In the face of ambiguity, refuse the temptation to guess.
      面對不明確的定義,拒絕猜測的誘惑。
    • There should be one– and preferably only one –obvious way to do it.
      用一種方法,最好只有一種方法來做一件事。
    • Although that way way not be obvious at first unless you’re Dutch.
      雖然一開始這種方法並不是顯而易見的,但誰叫你不是Python之父呢。
    • Now is better than never. Although never is often better than right now.
      做比不做好,但立馬去做有時還不如不做。
    • If the implementation is hard to explain, it’s a bad idea.
      如果實現很難說明,那它是個壞想法。
    • If the implementation is easy to explain, it may be a good idea.
      如果實現容易解釋,那它有可能是個好想法。
    • Namespaces are one honking great idea – let’s do more of those!
      命名空間是個絕妙的想法,讓我們多多地使用它們吧!

    道着重於方法,禪着重於態度,讓我們把這兩者相結合,做一個有追求的程序員,為成為軟件匠人而奮鬥吧。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 面試必問系列之JDK動態代理

    面試必問系列之JDK動態代理

    掃描文末二維碼或者微信搜索公眾號小李不禿,即可關注微信公眾號,獲取到更多 Java 相關內容。

    1. 帶着問題去學習

    面試中經常會問到關於 Spring 的代理方式有哪兩種?大家異口同聲的回答:JDK 動態代理和 CGLIB 動態代理。

    這兩種代理有什麼區別呢?JDK 動態代理的類通過接口實現,CGLIB 動態代理是通過子類來實現的。

    那 JDK 動態代理你了到底了解多少呢?有去看過代理對象的 class 文件么?下面兩個關於 JDK 動態代理的問題你能回答上來么?

    • 問題1:為什麼 JDK 動態代理要基於接口實現?而不是基於繼承來實現?
    • 問題2:JDK 動態代理中,目標對象調用自己的另一個方法,會經過代理對象么

    小李帶着大家更深入的了解一下 JDK 的動態代理。

    2. JDK 動態代理的寫法

    • JDK 動態代理需要這幾部分內容:接口、實現類、代理對象。
    • 代理對象需要繼承 InvocationHandler,代理類調用方法時會調用 InvocationHandlerinvoke 方法。
    • Proxy 是所有代理類的父類,它提供了一個靜態方法 newProxyInstance 動態創建代理對象。
    public interface IBuyService {
         void buyItem(int userId);
         void refund(int nums);
    }

     

    @Service
    public class BuyServiceImpl implements IBuyService {
        @Override
        public void buyItem(int userId) {
            System.out.println("小李不禿要買東西!小李不禿的id是: " + userId);
        }
        @Override
        public void refund(int nums) {
            System.out.println("商品過保質期了,需要退款,退款數量 :" + nums);
        }
    }

     

    public class JdkProxy implements InvocationHandler {

        private Object target;
        public JdkProxy(Object target) {
            this.target = target;
        }
        // 方法增強
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before(args);
            Object result = method.invoke(target,args);
            after(args);
            return result;
        }
        private void after(Object result) { System.out.println("調用方法后執行!!!!" ); }
        private void before(Object[] args) { System.out.println("調用方法前執行!!!!" ); }

        // 獲取代理對象
        public <T> getProxy(){
            return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),this);
        }
    }

     

    public class JdkProxyMain {
        public static void main(String[] args) {
            // 標明目標 target 是 BuyServiceImpl
            JdkProxy proxy = new JdkProxy(new BuyServiceImpl());
            // 獲取代理對象實例
            IBuyService buyItem = proxy.getProxy();
            // 調用方法
            buyItem.buyItem(12345);
        }
    }

    查看運行結果

    調用方法前執行!!!!
    小李不禿要買東西!小李不禿的id是: 12345
    調用方法后執行!!!!

    我們完成了對目標方法的增強,開始對代理對象進行一個更全面的分析。

    3. 剖析代理對象並解答問題

    剖析代理對象的前提得是有代理對象,動態代理的對象是在運行時期創建的,我們就沒辦法通過打斷點的方式進行分析了。但是我們可以通過反編譯 .class 文件進行分析。如何獲取到 .class 文件呢?

    通過在代碼中添加:System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") ,就能夠實現將動態代理對象的 class 文件寫入到磁盤中。代碼如下:

    public class JdkProxyMain {
        public static void main(String[] args) {
            // 代理對象的 class 文件寫入到磁盤中
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");
            // 標明目標 target 是 BuyServiceImpl
            JdkProxy proxy = new JdkProxy(new BuyServiceImpl());
            // 獲取代理對象實例
            IBuyService buyItem = proxy.getProxy();
            // 調用方法
            buyItem.buyItem(12345);
        }
    }

    在項目的根目錄下多了一個 $Proxy0.class 文件

    看一下這個文件的內容

    public final class $Proxy0 extends Proxy implements IBuyService {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m4;
        private static Method m0;

        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }

        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }

        public final void buyItem(int var1) throws  {
            try {
                super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }

        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }

        public final void refund(int var1) throws  {
            try {
                super.h.invoke(this, m4, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }

        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }

        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m3 = Class.forName("com.example.springtest.service.IBuyService").getMethod("buyItem", Integer.TYPE);
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m4 = Class.forName("com.example.springtest.service.IBuyService").getMethod("refund", Integer.TYPE);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

    動態代理對象 $Proxy0 繼承了 Proxy 類並且實現了 IBuyService 接口。那問題 1 的答案就出來了:動態代理對象默認繼承了 Proxy 對象,而且 Java 不支持多繼承,所以 JDK 動態代理要基於接口來實現。

    $Proxy0 重寫了 IBuyService 接口的方法,還有 Object 的方法。在重寫的方法中,統一調用 super.h.invoke 方法。super 指的是 Proxyh 代表 InvocationHandler,這裏就是 JdkProxy。所以這裏調用的是 JdkProxyinvoke 方法。

    所以每次調用 buyItem 方法的時候,會先打印出 調用方法前執行!!!!

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    before(args);
    // 通過反射調用方法
    Object result = method.invoke(target,args);
    after(args);
    return result;
    }
    private void after(Object result) { System.out.println("調用方法后執行!!!!" ); }
    private void before(Object[] args) { System.out.println("調用方法前執行!!!!" ); }

    問題 2 還沒解決呢,接着往下看

    @Service
    public class BuyServiceImpl implements IBuyService {
        @Override
        public void buyItem(int userId) {
            System.out.println("小李不禿要買東西!小李不禿的id是: " + userId);
            refund(100);
        }
        @Override
        public void refund(int nums) {
            System.out.println("商品過保質期了,需要退款,退款數量 :" + nums);
        }
    }

    上面這段代碼中,在 buyItem 調用內部的 refund 方法,那這個內部調用方法是否走代理對象呢?看一下執行結果:

    調用方法前執行!!!!
    小李不禿要買東西!小李不禿的id是: 12345
    商品過保質期了,需要退款,退款數量 :100
    調用方法后執行!!!!

    確實是沒有走代理對象,其實我們期待的結果是下面這樣的

    調用方法前執行!!!!
    小李不禿要買東西!小李不禿的id是: 12345
    調用方法前執行!!!!
    商品過保質期了,需要退款,退款數量 :100
    調用方法后執行!!!!
    調用方法后執行!!!!

    那為什麼會造成這種差異呢?

    因為內部調用 refund 方法的調用,相當於 this.refund(100),而這個 this 指的是 BuyServiceImpl 對象,而不是代理對象,所以refund 方法沒有得到增強

    4. 總結和延伸

    • 本篇文章了解了 JDK 動態代理的使用,通過分析 JDK 動態代理生成對象的 class 文件,解決了兩個問題:

      • 問題1:為什麼 JDK 動態代理要基於接口實現?而不是基於繼承來實現?
      • 解答:因為 JDK 動態代理生成的對象默認是繼承 Proxy ,Java 不支持多繼承,所以 JDK 動態代理要基於接口來實現。
      • 問題2:JDK 動態代理中,目標對象調用自己的另一個方法,會經過代理對象么
      • 解答:內部調用方法使用的對象是目標對象本身,被調用的方法不會經過代理對象。
    • 我們知道了 JDK 動態代理內部調用是不走代理對象的。那對於 @Transactional 和 @Async 等註解不起作用是不是就搞清楚為啥了?

    • 因為 @Transactional@Async 等註解是通過 Spring AOP 來進行實現的,如果動態代理使用的是 JDK 動態代理,那麼在方法的內部調用該方法中其它帶有該註解的方法,由於此時調用的不是動態代理對象,所以註解失效

    • 上面這些問題就是 JDK 動態代理的缺點,那 Spring 如何避免這個問題呢?就是另個一個動態代理:CGLIB 動態代理,我會在下篇文章進行分析。

    5. 參考

    • https://juejin.im/post/5d8a0799f265da5b7a752e7c#heading-6
    • https://blog.csdn.net/varyall/article/details/102952365

    6. 猜你喜歡

    • JSON的學習和使用

    • 學習反射看這一篇就夠了

    • 併發編程學習(一)Java 內存模型

    掃描下方二維碼即可關注微信公眾號小李不禿,一起高效學習 Java。

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

  • 看了Java的Class的源碼,我自閉了

    看了Java的Class的源碼,我自閉了

    java源碼之Class

    ​ 源碼的重要性不言而喻,雖然枯燥,但是也有拍案叫絕。這是我的源碼系列第二彈,後續還會一直更新,歡迎交流。String源碼可以看我的Java源碼之String,如有不足,希望指正。

    1.class這個類是什麼

    Class的本質也是一個類,只不過它是將我們定義類的共同的部分進行抽象,比如我們常定義的類都含有構造方法,類變量,函數,而Class這個類就是來操作這些屬性和方法的。當然我們常定義的類包含的類型都可以通過Class間接的來操作。而類的類型包含一般的類,接口,枚舉類型,註解類型等等。這麼說可能有點太理論,我們看下面這個例子:

    我們將生活中的一類事物抽象為一個類的時候,往往是因為他們具有相同的共性和不同的個性。定義一個類的作用就是將相同的共性抽離出來。一般的類都包含屬性和方法(行為),下面我們定義水果和汽車這兩個大類:

    代碼如下:

    汽車類:

    class Car{
    
        // 定義屬性
        private String name;
        private String color;
    
        /**
         * 定義兩個構造方法
         */
        public Car(){
    
        }
    
        public Car(String name,String color){
            this.name = name;
            this.color = color;
        }
    
        /**
         * 定義兩個普通方法(行為)
         */
        public void use(){
            
        }
        
        public void run(){
            
        }
    
        /**
         * 屬性的get和set方法
         * @return
         */
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
             this.color = color;
        }
    }
    
    
    

    水果類:

    class Fruit{
    
        // 定義屬性
        private String name;
        private int size;
    
        /**
         * 定義兩個構造方法
         */
        public Fruit(){
    
        }
    
        public Fruit(String name,int size){
            this.name = name;
            this.size =size;
        }
    
        /**
         * 定義兩個方法(行為)
         */
        public void use(){
            
        }
        
        public void doFruit(){
            
        }
    
        /**
         * 屬性的get和set方法
         * @return
         */
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getSize() {
            return size;
        }
    
        public void setSize(int size) {
            this.size = size;
        }
    }
    

    可以看到水果和汽車這兩個類都有共同的部分,也就是一個類共同的部分,那就是屬性和方法,而Class就是來操作我們定義類的屬性和方法。

    ​小試牛刀:通過Class這個類來獲取Fruit這個類中定義的方法;

    public static void main(String[] args) {
    
            Fruit fruit = new Fruit();
            Class fruitClass = fruit.getClass();
    
            Method[] fruitMethods = fruitClass.getMethods();
            System.out.println("方法個數:" + fruitMethods.length);
    
            for (Method method : fruitMethods) {
                //得到返回類型
                System.out.print("方法名稱和參數:" + method.getName() + "(");
                //取得某個方法對應的參數類型數組
                Class[] paramsType = method.getParameterTypes();
                for (Class paramType : paramsType) {
                    System.out.print(paramType.getTypeName() + " ");
                }
                System.out.print(")");
    
                Class returnType = method.getReturnType();
                System.out.println("返回類型:" + returnType.getTypeName());
            }
        }
    

    運行結果:

    方法個數:15
    方法名稱和參數:getName()返回類型:java.lang.String
    方法名稱和參數:setName(java.lang.String )返回類型:void
    方法名稱和參數:getSize()返回類型:int
    方法名稱和參數:setSize(int )返回類型:void
    方法名稱和參數:use()返回類型:void
    方法名稱和參數:doFruit()返回類型:void
    方法名稱和參數:wait()返回類型:void
    方法名稱和參數:wait(long int )返回類型:void
    方法名稱和參數:wait(long )返回類型:void
    方法名稱和參數:equals(java.lang.Object )返回類型:boolean
    方法名稱和參數:toString()返回類型:java.lang.String
    方法名稱和參數:hashCode()返回類型:int
    方法名稱和參數:getClass()返回類型:java.lang.Class
    方法名稱和參數:notify()返回類型:void
    方法名稱和參數:notifyAll()返回類型:void
    

    這裏可能有人疑惑了,Fruit類並沒有定義的方法為什麼會出現,如wait(),equals()方法等。這裏就有必要說一下java的繼承和反射機制。在繼承時,java規定每個類默認繼承Object這個類,上述這些並沒有在Fruit中定義的方法,都是Object中的方法,我們看一下Object這個類的源碼就會一清二楚:

     public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
    
     public final native void wait(long timeout) throws InterruptedException;
    
     public final void wait() throws InterruptedException {
            wait(0);
        }
    

    而Class類中的getMethods()方法默認會獲取父類中的公有方法,也就是public修飾的方法。所以Object中的公共方法也出現了。

    注: 要想獲得父類的所有方法(public、protected、default、private),可以使用apache commons包下的FieldUtils.getAllFields()可以獲取類和父類的所有(public、protected、default、private)屬性。

    是不是感覺非常的強大 ,當然,使用Class來獲取一些類的方法和屬性的核心思想就是利用了Java反射特性。萬物皆反射,可見反射的強大之處,至於反射的原理,期待我的下一個博客。

    2.常用方法的使用以及源碼分析

    2.1構造方法

    源碼如下:

     private Class(ClassLoader loader) {
            // Initialize final field for classLoader.  The initialization value of non-null
            // prevents future JIT optimizations from assuming this final field is null.
            classLoader = loader;
        }
    

    可以看到Class類只有一個構造函數,並且是私有的。也就是說不能通過new來創建這個類的實例。官方文檔的解釋:私有構造函數,僅Java虛擬機創建Class對象。我想可能就是為了安全,具體原因不是很了解。如果有了解的話,可以在評論區內共同的交流。

    Class是怎麼獲取一個實例的。

    那麼既然這個class構造器私有化,那我們該如何去構造一個class實例呢,一般採用下面三種方式:

    1.運用.class的方式來獲取Class實例。對於基本數據類型的封裝類,還可以採用.TYPE來獲取相對應的基本數據類型的Class實例,如下的示例。

     // 普通類獲取Class的實例。接口,枚舉,註解,都可以通過這樣的方式進行獲得Class實例
    Class fruitClass = Fruit.class;
    
    // 基本類型和封裝類型獲得Class實例的方式,兩者等效的
    Class intClass = int.class;
    Class intClass1 = Integer.TYPE;
    

    下面的表格兩邊等價:

    boolean.class Boolean.TYPE
    char.class Character.TYPE
    byte.class Byte.TYPE
    short.class Short.TYPE
    int.class Integer.TYPE
    long.class Long.TYPE
    float.class Float.TYPE
    double.class Double.TYPE
    void.class Void.TYPE

    但是這種方式有一個不足就是對於未知的類,或者說不可見的類是不能獲取到其Class對象的。

    2.利用對象.getClass()方法獲取該對象的Class實例;

    這是利用了Object提供的一個方法getClass() 來獲取當著實例的Class對象,這種方式是開發中用的最多的方式,同樣,它也不能獲取到未知的類,比如說某個接口的實現類的Class對象。

    Object類中的getClass()的源碼如下:

    public final native Class<?> getClass();
    

    源碼說明:

    可以看到,這是一個native方法(一個Native Method就是一個java調用非java代碼的接口),並且不允許子類重寫,所以理論上所有類型的實例都具有同一個 getClass 方法。

    使用:

     Fruit fruit = new Fruit();
     Class fruitClass = fruit.getClass();
    

    3.使用Class類的靜態方法forName(),用類的名字獲取一個Class實例(static Class forName(String className) ),這種方式靈活性最高,根據類的字符串全名即可獲取Class實例,可以動態加載類,框架設計經常用到;

    源碼如下:

        /*
         由於方法區 Class 類型信息由類加載器和類全限定名唯一確定,所以參數name必須是全限定名,
         參數說明   name:class名,initialize是否加載static塊,loader 類加載器
         */
        public static Class<?> forName(String name, boolean initialize,
                                       ClassLoader loader)
            throws ClassNotFoundException
        {
            Class<?> caller = null;
            
            // 1.進行安全檢查
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
               ....
                }
            }
            // 2.調用本地的方法
            return forName0(name, initialize, loader, caller);
        }
       
        // 3.核心的方法
        private static native Class<?> forName0(String name, boolean initialize,
                                                ClassLoader loader,
                                                Class<?> caller)
          throws ClassNotFoundException;
    
       /* 
        這個 forName是上述方法的重載,平時一般都使用這個 方法默認使用調用者的類加載器,將類的.class文件加載     到 jvm中
        這裏傳入的initialize為true,會去執行類中的static塊
        */
        public static Class<?> forName(String className)
                    throws ClassNotFoundException {
            Class<?> caller = Reflection.getCallerClass();
            return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
        }
    
    

    源碼說明已在註釋中說明,有些人會疑惑, static native Class<?> forName0()這個方法的實現。

    這就要說到java的不完美的地方了,Java的不足除了體現在運行速度上要比傳統的C++慢許多之外,Java無法直接訪問到操作系統底層(如系統硬件等),為此Java使用native方法來擴展Java程序的功能。有關native的方法請移步這裏。

    基本使用:

     Class fruitClass = Class.forName("cn.chen.test.util.lang.Fruit");
    

    : 這種方式必須使用類的全限定名,,這是因為由於方法區 Class 類型信息由類加載器和類全限定名唯一確定,否則會拋出ClassNotFoundException的異常。

    2.2一般方法以及源碼分析:

    Class類的一般的方法總共有六十多種,其實看到這麼多方法咱也不要慫,這裏面還有很多重載的方法,根據二八原則,我們平時用的也就那麼幾個方法,所以這裏只對以下幾個方法的使用和實現進行交流,其他的方法可以移步Java官方文檔:

    2.2.1 獲得類的構造方法

    這個方法主要是用來了解一個類的構造方法有哪些,包含那些參數,特別是在單例的模式下。一般包含的方法如下:

    • public Constructor[] getConstructors() :獲取類對象的所有可見的構造函數

    • public Constructor[] getDeclaredConstructors():獲取類對象的所有的構造函數

    • public Constructor getConstructor(Class… parameterTypes): 獲取指定的可見的構造函數,參數為:指定構造函數的參數類型數組,如果該構造函數不可見或不存在,會拋出 NoSuchMethodException 異常

    • public Constructor getDeclaredConstructor(Class… parameterTypes) :獲取指定的構造函數,參數為:指定構造函數的參數類型數組,無論構造函數可見性如何,均可獲取

    基本使用:

    Constructor[] constructors = fruitClass.getConstructors();
     for (Constructor constructor : constructors) {
                System.out.println("獲得共有的構造方法:"+constructor);
            }
    

    輸出結果:

    獲得共有的構造方法:public cn.chen.test.util.lang.Fruit()
    獲得共有的構造方法:public cn.chen.test.util.lang.Fruit(java.lang.String,int)
    

    可以看到我們前面定義的來個構造方法,都被打印出來了。注意getConstructors()只能獲得被public修飾的構造方法,如果要獲得被(protected,default,private)修飾的構造方法,就要使用的getDeclaredConstructors()這個方法了。接下來,修改Fruit中的一個構造方法為private:

     private  Fruit(String name,int size){
            this.name = name;
            this.size =size;
        }
    

    使用getConstructors()和getDeclaredConstructors()着兩個方法進行測試:

           Class fruitClass = Fruit.class;       
           Constructor[] constructors = fruitClass.getConstructors();
           Constructor[] constructors1 = fruitClass.getDeclaredConstructors();
    
            for (Constructor constructor : constructors) {
                System.out.println("獲得共有的構造方法:"+constructor);
            }
    
            System.out.println("=================================================");
            for (Constructor constructor : constructors1) {
                System.out.println("獲得所有的構造方法:"+constructor);
            }
    

    輸出結果:

    獲得共有的構造方法:public cn.chen.test.util.lang.Fruit()
    ===================分隔線=============================
    獲得所有的構造方法:public cn.chen.test.util.lang.Fruit()
    獲得所有的構造方法:private cn.chen.test.util.lang.Fruit(java.lang.String,int)
    

    可以看到兩者的區別。所以,反射在一定程度上破壞了java的封裝特性。畢竟人無完人,語言亦是一樣。

    getConstructors()的源碼分析:

    public Constructor<?>[] getConstructors() throws SecurityException {
              
            // 1.檢查是否允許訪問。如果訪問被拒絕,則拋出SecurityException。
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
            return copyConstructors(privateGetDeclaredConstructors(true));
        }
        
     private static <U> Constructor<U>[] copyConstructors(Constructor<U>[] arg) {
          // 2.使用克隆,得到當前類的所有構造函數   
          Constructor<U>[] out = arg.clone();
         // 3.使用ReflectionFactory構造一個對象,也是不使用構造方法構造對象的一種方式。
            ReflectionFactory fact = getReflectionFactory();
         // 4.遍歷,將構造函數進行拷貝返回,注意在調用fact.copyConstructor(out[i])這個方法的時候,還會進行安全檢查,用的就是下面的LangReflectAccess() 這個方法。
            for (int i = 0; i < out.length; i++) {
                out[i] = fact.copyConstructor(out[i]);
            }
            return out;
        }
    
    
    
     private static LangReflectAccess langReflectAccess() {
            if (langReflectAccess == null) {
                Modifier.isPublic(1);
            }
    
            return langReflectAccess;
        } 
    
    

    通過打斷點調試,可以看到下面的信息:

    代碼的調用邏輯在註釋里已進行說明。

    2.2.2 獲得屬性

    主要獲取類的屬性字段,了解這個類聲明了那些字段。

    一般有四個方法:

    • public Field[] getFields():獲取所有可見的字段信息,Field數組為類中聲明的每一個字段保存一個Field 實例
    • public Field[] getDeclaredFields():獲取所有的字段信息
    • public Field getField(String name) :通過字段名稱獲取字符信息,該字段必須可見,否則拋出異常
    • public Field getDeclaredField(String name) :通過字段名稱獲取可見的字符信息

    基本使用:

    首先我們在Fruit的類中加入一個public修飾的屬性:

        public double weight;
    
    Class fruitClass = Fruit.class; 
    Field[] field2 = fruitClass.getFields();
            for (Field field : field2) {
                System.out.println("定義的公有屬性:"+field);
            }
    
            Field[] fields = fruitClass.getDeclaredFields();
            for (Field field : fields) {
                System.out.println("定義的所有屬性:"+field);
            }
    

    輸出結果:

    定義的公有屬性:public double cn.chen.test.util.lang.Fruit.weight
    ========================分隔線============================
    定義的所有屬性:private java.lang.String cn.chen.test.util.lang.Fruit.name
    定義的所有屬性:private int cn.chen.test.util.lang.Fruit.size
    定義的所有屬性:public double cn.chen.test.util.lang.Fruit.weight
    

    源碼分析,就以getFileds()這個方法為例,涉及以下幾個方法:

    public Field[] getFields() throws SecurityException {
            // 1.檢查是否允許訪問。如果訪問被拒絕,則拋出SecurityException。
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
            return copyFields(privateGetPublicFields(null));
        }
     
     private static Field[] copyFields(Field[] arg) {
             // 2. 聲明一個Filed的數組,用來存儲類的字段 
            Field[] out = new Field[arg.length];
            //  3.使用ReflectionFactory構造一個對象,也是不使用構造方法構造對象的一種方式。
            ReflectionFactory fact = getReflectionFactory();
           // 4.遍歷,將字段複製后返回。
            for (int i = 0; i < arg.length; i++) {
                out[i] = fact.copyField(arg[i]);
            }
            return out;
        }
        
     public Field copyField(Field var1) {
            return langReflectAccess().copyField(var1);
        }
     
    // 再次檢查屬性的訪問權限
      private static LangReflectAccess langReflectAccess() {
            if (langReflectAccess == null) {
                Modifier.isPublic(1);
            }
    
            return langReflectAccess;
        }
    

    2.2.3 獲得一般方法

    就是獲取一個類中的方法,一般有以下幾個方法:

    • public Method[] getMethods(): 獲取所有可見的方法

    • public Method[] getDeclaredMethods() :獲取所有的方法,無論是否可見

    • public Method getMethod(String name, Class… parameterTypes)

      參數說明:

    1. 通過方法名稱、參數類型獲取方法
    2. 如果你想訪問的方法不可見,會拋出異常
    3. 如果你想訪問的方法沒有參數,傳遞 null作為參數類型數組,或者不傳值)
    • public Method getDeclaredMethod(String name, Class… parameterTypes)
    1. 通過方法名稱、參數類型獲取方法
    2. 如果你想訪問的方法沒有參數,傳遞 null作為參數類型數組,或者不傳值)

    基本使用:

    //在fruit中定義一個這樣的方法
     private  void eat(String describe){
            System.out.println("通過getMethod()方法調用了eat()方法:  "+describe);
        }
    

    調用這個方法:

            Class fruitClass = Fruit.class;
            Method method = fruitClass.getDeclaredMethod("eat",String.class);
            method.setAccessible(true);
            method.invoke(fruitClass.newInstance(),"我是該方法的參數值");
    

    輸出結果:

      通過getMethod()方法調用了eat()方法:我是該方法的參數值
    

    分析getDeclaredMethod()涉及的源碼:

    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException {
            // 1.檢查方法的修飾符
            checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
            // 2.searchMethods()方法的第一個參數確定這個方法是不是私有方法,第二個參數我們定義的方法名,第三個參數就是傳入的方法的參數類型
            Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
            if (method == null) {
                throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
            }
            return method;
        }
    
    // 這個方法就是通過傳入的方法名找到我們定義的方法,然後使用了Method的copy()方法返回一個Method的實例,我們通過操作mehtod這個實例就可以操作我們定義的方法。
     private static Method searchMethods(Method[] methods,
                                            String name,
                                            Class<?>[] parameterTypes)
        {
            Method res = null;
            String internedName = name.intern();
            for (int i = 0; i < methods.length; i++) {
                Method m = methods[i];
                if (m.getName() == internedName
                    && arrayContentsEq(parameterTypes, m.getParameterTypes())
                    && (res == null
                        || res.getReturnType().isAssignableFrom(m.getReturnType())))
                    res = m;
            }
    
            return (res == null ? res : getReflectionFactory().copyMethod(res));
        }
    
     public Method copyMethod(Method var1) {
            return langReflectAccess().copyMethod(var1);
        }
    
     
    // 檢查屬性的訪問權限
      private static LangReflectAccess langReflectAccess() {
            if (langReflectAccess == null) {
                Modifier.isPublic(1);
            }
    
            return langReflectAccess;
        }
    

    2.2.4 判斷類的類型的方法

    這類型的方法顧名思義,就是來判斷這個類是什麼類型,是接口,註解,枚舉,還是一般的類等等。部分方法如下錶

    boolean isAnnotation()判斷是不是註解
    boolean isArray() 判斷是否為數組
    boolean isEnum()判斷是否為枚舉類型
    boolean isInterface() 是否為接口類型
    boolean isMemberClass()當且僅當基礎類是成員類時,返回“true”
    boolean isPrimitive()確定指定的“類”對象是否表示原始類型。
    boolean isSynthetic()如果這個類是合成類,則返回’ true ‘;否則返回“false”。

    基本用法:

    // 定義一個接口:
    interface  Animal{
        public void run();
    }
    

    判斷是不是一個接口:

    Class AnimalClass = Animal.class;
     boolean flag = AnimalClass.isInterface();
     System.out.println(flag);
    

    輸出結果:

    true
    

    源碼分析isInterface():

     public native boolean isInterface();
    

    這是一個native方法,大家都知道native方法是非Java語言實現的代碼,供Java程序調用的,因為Java程序是運行在JVM虛擬機上面的,要想訪問到比較底層的與操作系統相關的就沒辦法了,只能由靠近操作系統的語言來實現。

    2.2.5 toString()方法

    將對象轉換為字符串。字符串表示形式是字符串“類”或“接口”,後跟一個空格,然後是該類的全限定名。

    基本使用:

    // 這是前面定義的兩個類Fruit和Car,Car是一個接口
     Class fruitClass = Fruit.class;
     Class AnimalClass = Animal.class;
     System.out.println(AnimalClass.toString());
     System.out.println(fruitClass.toString());
    

    輸出結果:

    // 格式  字符串“類”或“接口”,後跟一個空格,然後是該類的全限定名
    interface cn.chen.test.util.lang.Animal
    class cn.chen.test.util.lang.Fruit
    

    源碼如下:

     public String toString() {
           // 先是判斷是接口或者類,然後調用getName輸出類的全限定名
            return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
                + getName();
        }
    
      public native boolean isInterface();
      public native boolean isPrimitive();
    

    追本溯源,方能闊步前行。

    參考資料

    ​ https://blog.csdn.net/x_panda/article/details/17120479

    ​ https://juejin.im/post/5d4450fbe51d4561ce5a1be1

    ​ JavaSE的官方文檔

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

    【其他文章推薦】

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

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

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

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

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

  • Java Jar 包加密 — XJar

    Java Jar 包加密 — XJar

    Java Jar 包加密

    一、緣由

      Java的 Jar包中的.class文件可以通過反彙編得到源碼。這樣一款應用的安全性就很難得到保證,別人只要得到你的應用,不需花費什麼力氣,就可以得到源碼。

      這時候就需要對jar進行加密處理。

    二、技術&工具

      XJar

      GitHub:https://github.com/core-lib/xjar

      碼雲:https://gitee.com/core-lib/xjar?_from=gitee_search

      maven集成 XJar

     

      GitHub:https://github.com/core-lib/xjar-maven-plugin

      碼雲:https://gitee.com/core-lib/xjar-maven-plugin?_from=gitee_search

      xjar-agent-hibernate

      GitHub:https://github.com/core-lib/xjar-agent-hibernate

      碼雲:https://gitee.com/core-lib/xjar-agent-hibernate?_from=gitee_search

      go語言、maven、eclipse

      文檔可以到github、碼雲上去了解,這裏只描述使用過程,親測可用!

    三、實現過程

      這裏使用的maven版本是:apache-maven-3.6.3,低版本的沒測試過

      1、XJar

       1-1.在github或碼雲上下載該項目,導入eclipse,然後新建一個main類,填入參數,直接運行得到一個xjar.go 和 加密后的jar包【xx-encrypted.jar】。

        注意:這種直接在項目中跑mian,不提倡,會導致jar包中包含這段代碼,導緻密碼泄露,所以要通過命令行的方式來執行這段代碼。

        

        1-2.這時候可以用反編譯軟件 jb-gui 打開jar看看加密的效果,這時候反編譯軟件已經看不到.class文件的源碼了

     

     

         1-3.加密后的jar包,不能直接用原來的java 命令來執行,需要用到同時生成的xjar.go文件,執行命令 go build xjar.go

          這裏要等待一小會,等待編譯出目標文件xjar.exe

          

     

     

           

          1-4. 最後執行命令,xjar java -jar /path/to/encrypted.jar,即可運行加密后的jar包

           注意:Spring Boot + JPA(Hibernate) 啟動會報錯

                       1-5:沒有採用 Spring Boot + JPA(Hibernate) 技術的可以略過以下步驟。

          a、到碼雲、GitHub上下載  xjar-agent-hibernate  項目

          b、導入eclipse 打包出jar包

          c、然後執行命令  xjar java -javaagent:xjar-agent-hibernate-v1.0.0.jar -jar path\wx-encrypted.jar,即可正常運行

          

       2、maven集成 XJar

        第二種方式就比較簡單了,直接在項目中引入xjar-maven-plugin,然後打包就可以了,其他操作方式和第一種類似

        注意:密碼最好採用命令行方式

    <pluginRepositories>
      <pluginRepository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
      </pluginRepository>
    </pluginRepositories>

           

     <plugin> <groupId>com.github.core-lib</groupId> <artifactId>xjar-maven-plugin</artifactId> <version>4.0.0</version> <executions> <execution> <goals> <goal>build</goal> </goals> <phase>install</phase> <configuration> <password>1233445</password> <includes>
                       <!----> <include>/com/xxx/xxx/**/*.class</include> </includes> <!-- 無需加密的資源路徑表達式 --> <excludes> <exclude>static/**</exclude> <exclude>META-INF/resources/**</exclude> </excludes> <!-- 源jar所在目錄 --> <sourceDir>path\</sourceDir> <!-- 源jar名稱 --> <sourceJar>xxx.jar</sourceJar> <!-- 目標jar存放目錄 --> <targetDir>path\test2</targetDir> <!-- 目標jar名稱 --> <targetJar>xxx-encrypted.jar</targetJar> </configuration> </execution> </executions> </plugin>

    四、後記

      其實所有軟件,都可以被破解,只是破解過程是簡單還是複雜、以及破解成本的高低。

      最關鍵的還是自己軟件要更新迭代的快,這樣才能把模仿者遠遠甩在身後。

      轉發請註明出處!!!

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

  • 程序員必須掌握的Java 框架,小白學會之後15k不是問題

    程序員必須掌握的Java 框架,小白學會之後15k不是問題

    Spring 的核心特性是什麼?Spring 優點?

    Spring 的核心是控制反轉(IoC)和面向切面(AOP)

    Spring 優點:

    程序員必須掌握的Java 框架,學會之後50k不是問題
    (1)方便解耦,簡化開發 (高內聚低耦合)

    Spring 就是一個大工廠(容器),可以將所有對象創建和依賴關係維護,交給 Spring管理

    spring 工廠是用於生成 bean

    (2)AOP 編程的支持

    Spring 提供面向切面編程,可以方便的實現對程序進行權限攔截、運行監控等功能

    (3) 聲明式事務的支持

    只需要通過配置就可以完成對事務的管理,而無需手動編程

    (4) 方便程序的測試

    Spring 對 Junit4 支持,可以通過註解方便的測試 Spring 程序

    (5)方便集成各種優秀框架

    Spring 不排斥各種優秀的開源框架,其內部提供了對各種優秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持

    (6) 降低 JavaEE API 的使用難度

    Spring 對 JavaEE 開發中非常難用的一些 API(JDBC、JavaMail、遠程調用等),都提供了封裝,使這些 API 應用難度大大降低

    spring 框架中需要引用哪些 jar 包,以及這些 jar 包的用途

    4 + 1 : 4 個 核 心 ( beans 、 core 、 context 、 expression ) + 1 個 依 賴(commons-loggins…jar)

    理解 AOP、IoC 的基本原理;

    IOC:控制反轉(IoC)與依賴注入(DI)是同一個概念,

    控制反轉的思想:

    傳統的 java 開發模式中,當需要一個對象時,我們會自己使用 new 或者getInstance 等直接或者間接調用構造方法創建一個對象。而在 spring 開發模式中,spring 容器使用了工廠模式為我們創建了所需要的對象,不需要我們自己創建了,直接調用 spring 提供的對象就可以了

    引入 IOC 的目的:

    (1)脫開、降低類之間的耦合;(2)倡導面向接口編程、實施依賴倒換原則;

    (3)提高系統可插入、可測試、可修改等特性

    AOP:面向切面編程(AOP)面向切面編程思想:

    在面向對象編程(oop)思想中,我們將事物縱向抽成一個個的對象。而在面向切面編程中,我們將一個個的對象某些類似的方面橫向抽成一個切面,對這個切面進行一些如權限控制、事物管理,記錄日誌等公用操作處理的過程。

    切面:簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。

    AOP 底層:動態代理。

    如果是接口採用 JDK 動態代理,如果是類採用 CGLIB 方式實現動態代理。

    AOP 的一些場景應用;

    AOP 用來封裝橫切關注點,具體可以在下面的場景中使用:

    Authentication 權限

    Caching 緩存

    Context passing 內容傳遞

    Error handling 錯誤處理

    Lazy loading 懶加載

    Debugging調試

    logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準

    Performance optimization 性能優化

    Persistence持久化

    Resource pooling 資源池

    Synchronization 同步

    Transactions 事務

    spring 注入的幾種方式
    (1)構造方法注入

    (2)setter 注入

    (3)基於註解

    Spring 中 Bean 的作用域有哪些

    作用域:用於確定 spring 創建 bean 實例個數

    取值(常用的兩個):

    singleton 單例,默認值。prototype 多例,一個 bean 的定義可以有多個實例。每執行一次 getBean 將獲得一個實例。

    請介紹一下 bean 的生命周期

    (1)bean 定義:在配置文件裏面用來進行定義。

    (2)bean 初始化:有兩種方式初始化:在配置文件中通過指定 init-method 屬性來完成實現
    org.springframwork.beans.factory.InitializingBean 接口

    (3)bean 調用:有三種方式可以得到 bean 實例,並進行調用

    (4)bean 銷毀:銷毀有兩種方式使用配置文件指定的 destroy-method 屬性實現
    org.springframwork.bean.factory.DisposeableBean 接口

    Spring 中自動裝配的方式有哪些

    no:不進行自動裝配,手動設置 Bean 的依賴關係。

    byName:根據 Bean 的名字進行自動裝配。

    byType:根據 Bean 的類型進行自動裝配。

    constructor:類似於 byType,不過是應用於構造器的參數,如果正好有一個 Bean與構造器的參數類型相同則可以自動裝配,否則會導致錯誤。autodetect:如果有默認的構造器,則通過 constructor 的方式進行自動裝配,否則使用 byType 的方式進行自動裝配。

    (自動裝配沒有自定義裝配方式那麼精確,而且不能自動裝配簡單屬性(基本類型、字符串等),在使用時應注意。)

    @Resource 和 @Autowired 區別?分別用在什麼場景?

    (1)共同點:兩者都可以寫在字段和 setter 方法上。兩者如果都寫在字段上,那麼就不需要再寫 setter 方法。

    (2)不同點:

    @Autowired

    @Autowired為Spring提供的註解,需要導入包
    org.springframework.beans.factory.annotation.Autowired;只按照 byType 注入。@Autowired 註解是按照類型(byType)裝配依賴對象,默認情況下它要求依賴對象必須存在,如果允許 null 值,可以設置它的 required 屬性為 false。如果我們想使用按照名稱(byName)來裝配,可以結合@Qualifier 註解一起使用。

    @Resource

    @Resource 默 認 按 照 ByName 自 動 注 入 , 由 J2EE 提 供 , 需 要 導 入 包javax.annotation.Resource。@Resource 有兩個重要的屬性:name 和 type,而 Spring將@Resource 註解的 name 屬性解析為 bean 的名字,而 type 屬性則解析為 bean的類型。所以,如果使用 name 屬性,則使用 byName 的自動注入策略,而使用type 屬性時則使用 byType 自動注入策略。如果既不制定 name 也不制定 type 屬性,這時將通過反射機制使用 byName 自動注入策略。

    Hibernate 和 mybatis 的區別?

    (1)兩者最大的區別

    針對簡單邏輯,Hibernate 與 MyBatis 都有相應的代碼生成工具,可以生成簡單基本的 DAO 層方法。

    針對高級查詢,MyBatis 需要手動編寫 SQL 語句,以及 ResultMap,而 Hibernate有良好的映射機制,開發者無需關心 SQL 的生成與結果映射,可以更專註於流程。

    (2)開發難度對比

    Hibernate 的開發難度大於 MyBatis,主要由於 Hibernate 比較複雜,龐大,學習周期比較長。

    MyBatis 則相對簡單,並且 MyBatis 主要依賴於生氣了的書寫,讓開發者剛進更熟悉。

    (3)sql 書寫比較

    Hibernate 也可以自己寫 sql 來指定需要查詢的字段,但這樣就破壞了Hibernate 開發的簡潔性,不過 Hibernate 具有自己的日誌統計。

    MyBatis 的 sql 是手動編寫的,所以可以按照要求指定查詢的字段,不過沒有自己的日誌統計,所以要藉助 Log4j 來記錄日誌。

    (4)數據庫擴展性計較Hibernate 與數據庫具體的關聯在 XML 中,所以 HQL 對具體是用什麼數據庫
    並不是很關心MyBatis 由於所有 sql 都是依賴數據庫書寫的,所以擴展性、遷移性比較差。

    (5)緩存機制比較

    Hibernate 的二級緩存配置在 SessionFactory 生成配置文件中進行詳細配置,然後再在具體的表對象映射中配置那種緩存。

    MyBatis 的二級緩存配置都是在每個具體的表對象映射中進行詳細配置,這樣針對不同的表可以自定義不同的緩衝機制,並且 MyBatis 可以在命名空間中共享相同的緩存配置和實例,通過 Cache-ref 來實現。

    兩者比較,因為 Hibernate 對查詢對象有着良好的管理機制,用戶無需關心 SQL,所以在使用二級緩存時如果出現臟數據,系統會報出錯誤提示。 而 MyBatis 在這一方面使用二級緩存時需要特別小心,如果不能完全去頂數據更新操作的波及範圍,避免 cache 的盲目使用,否則,臟數據的出現會給系統的正常運行帶來很大的隱患。

    mybatis 是如何工作的?

    一、Mybatis 工作原理圖

    mybatis 原理圖如下所示:

    二、工作原理解析

    mybatis 應用程序通過 SqlSessionFactoryBuilder 從 mybatis-config.xml 配置文件(也可以用 Java 文件配置的方式,需要添加@Configuration)來構建 SqlSessionFactory(SqlSessionFactory 是線程安全的);

    然後,SqlSessionFactory 的實例直接開啟一個 SqlSession,再通過 SqlSession 實例獲得 Mapper 對象並運行 Mapper 映射的 SQL 語句,完成對數據庫的 CRUD 和事務提交,之後關閉 SqlSession。說明:SqlSession 是單線程對象,因為它是非線程安全的,是持久化操作的獨享對象,類似 jdbc 中的 Connection,底層就封裝了 jdbc 連接。

    詳細流程如下:

    (1)、加載 mybatis 全局配置文件(數據源、mapper 映射文件等),解析配置文件,MyBatis 基於 XML 配置文件生成 Configuration,和一個個 MappedStatement(包括了參數映射配置、動態 SQL 語句、結果映射配置),其對應着標籤項。

    (2)、SqlSessionFactoryBuilder 通過 Configuration 對象生成 SqlSessionFactory,用來開啟 SqlSession。

    (3)、SqlSession 對象完成和數據庫的交互:

    a、用戶程序調用 mybatis 接口層 api(即 Mapper 接口中的方法)

    b、SqlSession 通過調用 api 的 Statement ID 找到對應的 MappedStatement 對象

    c、通過 Executor(負責動態 SQL 的生成和查詢緩存的維護)將 MappedStatement對象進行解析,sql 參數轉化、動態 sql 拼接,生成 jdbc Statement 對象

    d、JDBC 執行 sql。

    e、藉助 MappedStatement 中的結果映射關係,將返回結果轉化成 HashMap、JavaBean 等存儲結構並返回。

    Hibernate 對象有幾個狀態值?

    Transient 瞬時 :對象剛 new 出來,還沒設 id,設了其他值。

    Persistent 持久:調用了 save()、saveOrUpdate(),就變成 Persistent,有 id

    Detached 脫管 : 當 session close()完之後,變成 Detached。

    簡述 Springmvc 的流程;

    spring 工作的流程

    流程如下:(1)用戶發起請求到前端控制器(DispatcherServlet),該控制器會過濾出哪些請求可以訪問 Servlet、哪些不能訪問。就是 url-pattern 的作用,並且會加載springmvc.xml 配置文件。

    (2)前端控制器會找到處理器映射器(HandlerMapping),通過 HandlerMapping完成 url 到 controller 映射的組件,簡單來說,就是將在 springmvc.xml 中配置的或者註解的 url 與對應的處理類找到並進行存儲,用 map<url,handler>這樣的方式來存儲。

    (3)HandlerMapping 有了映射關係,並且找到 url 對應的處理器,HandlerMapping就會將其處理器(Handler)返回,在返回前,會加上很多攔截器。

    (4)DispatcherServlet 拿到 Handler 后,找到 HandlerAdapter(處理器適配器),通過它來訪問處理器,並執行處理器。

    (5)執行處理器

    (6)處理器會返回一個 ModelAndView 對象給 HandlerAdapter

    (7) 通 過 HandlerAdapter 將 ModelAndView 對 象 返 回 給 前 端 控 制 器(DispatcherServlet)

    (8)前端控制器請求視圖解析器(ViewResolver)去進行視圖解析,根據邏輯視圖名解析成真正的視圖(jsp),其實就是將 ModelAndView 對象中存放視圖的名稱進行查找,找到對應的頁面形成視圖對象

    (9)返回視圖對象到前端控制器。

    (10)視圖渲染,就是將 ModelAndView 對象中的數據放到 request 域中,用來讓頁面加載數據的。

    (11)通過第 8 步,通過名稱找到了對應的頁面,通過第 10 步,request 域中有了所需要的數據,那麼就能夠進行視圖渲染了。最後將其返回即可。

    Springmvc 和 Springboot 有什麼區別?

    Spring MVC 是基於 Spring 的一個 MVC 框架 ;

    Spring Boot 是基於 Spring4 的條件註冊的一套快速開發整合包。

    Springboot 為什麼配置簡單?(即它自動做了什麼操作才能簡化程序員的操作)
    主要是使用了 spring3 之後提供的註解,來代替 xml 文件的配置,最核心的是以下兩個註解

    @Configuration,標註在類上,相當於定義一個配置類,一份 spring 的配置文件

    @Bean,類似於 spring 配置文件中的通過這兩個註解就可以用 java 代碼的方式來完成相關 spring 配置

    持久層設計要考慮的問題有哪些?請談一下你用過的持久層框架都有哪些?

    “持久”就是將數據保存到可掉電式存儲設備中以便今後使用,簡單的說,就是將內存中的數據保存到關係型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專註於實現數據持久化的相對獨立的層面。

    持久層設計的目標包括:

    數據存儲邏輯的分離,提供抽象化的數據訪問接口。

    數據訪問底層實現的分離,可以在不修改代碼的情況下切換底層實現。

    資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。

    數據抽象,提供更面向對象的數據操作。

    持久層框架有: Hibernate、MyBatis、TopLink、 Guzz、 jOOQ、 Spring Data、ActiveJDBC

    需要更多技術文檔可以在後台私信【學習】

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

  • 005.OpenShift訪問控制-權限-角色

    005.OpenShift訪問控制-權限-角色

    一 Kubetcl namespace

    1.1 namespace描述


    Kubernetes namespace提供了將一組相關資源組合在一起的機制。在Red Hat OpenShift容器平台中,project是一個帶有附加註釋的Kubernetes namespace。

    namespace提供以下特性:

    1. 命名資源,以避免基本的命名衝突;
    2. 將管理權限授予受信任的用戶;
    3. 限制用戶資源消耗的能力;
    4. 用戶和用戶組隔離。

    1.2 project


    project提供了一種機制,通過這種機制可以管理普通用戶對資源的訪問。project允許一組用戶獨立於其他組組織和管理其內容,必須允許用戶訪問項目。如果允許創建項目,用戶將自動訪問自己的項目。

    項目可以有單獨的name、display name和description。

    name是項目的唯一標識符,在使用CLI工具或API時都是基於name,name的最大長度為63個字符。

    display name是項目在web控制台中显示的方式(默認為name)。

    description是項目的更詳細描述,並且在web控制台中也可見。

    以下組件適用於項目:

    • Object:pod、service、rc等;
    • Policies:決定用戶可以或不能對對象執行哪些操作的規則;
    • Constraints:可以限制的每種對象的配額。

    1.3 cluster管理


    集群管理員可以創建項目並將項目的管理權限委託給任何用戶。在OpenShift容器平台中,項目用於對相關對象進行分組和隔離。

    管理員可以讓用戶訪問某些項目,允許他們創建自己的項目,並在單個項目中賦予他們管理權限。

    管理員可以將角色應用於允許或限制其創建項目能力的用戶和組,同時可以在用戶初始登錄之前分配角色。

    限制項目創建:從通過身份驗證的用戶和組中刪除self-provisioning集群角色,將拒絕任何新項目的權限。

    [root@master ~]$ oc adm policy remove-cluster-role-from-group \

    self-provisioner \

    system:authenticated \

    system:authenticated:oauth

    授予項目創建:項目創建授予具有self-供應者角色和self-provisione集群角色綁定的用戶。默認情況下,所有經過身份驗證的用戶都可以使用這些角色。

    [root@master ~]$ oc adm policy add-cluster-role-to-group \

    self-provisioner \

    system:authenticated \

    system:authenticated:oauth

    1.4 創建project


    如果項目創建權限被授予用戶,則可以使用oc命令創建project。

    [root@master ~]$ oc new-project demoproject \

    –description=”Demonstrate project creation” \

    –display-name=”demo_project”

    二 OpenShift角色

    2.1 角色概述


    role具有不同級別的訪問和策略,包括集群和本地策略。user和group可以同時與多個role關聯。運行oc description命令查看角色及其綁定的詳細信息。

    在集群策略中具有cluster-admin缺省角色的用戶可以查看集群策略和所有本地策略。在給定的本地策略中具有admin缺省角色的用戶可以基於per-project查看策略。

    可通過以下命令查看當前的集群綁定集,其中显示綁定到不同角色的用戶和組。

    [root@demo ~]# oc describe clusterPolicyBindings :default

    2.2 查看本地policy


    儘管本地角色列表及其關聯的規則集在本地策略中是不可查看的,但是所有缺省角色仍然適用,並且可以添加到用戶或組中,cluster-admin缺省角色除外。但是,本地綁定是可見的。

    可通過以下命令查看當前的本地綁定,其中显示綁定到不同角色的用戶和組。

    [root@demo ~]# oc describe policyBindings :default

    提示:默認情況下,在本地策略中,只會列出admin角色的綁定。但是,如果將其他默認角色添加到本地策略中的用戶和組,也會列出它們。

    2.3 管理role綁定


    向用戶或組添加或綁定角色,從而實現向用戶或組提供角色授予的相關訪問權限。可以使用oc adm policy命令在用戶和組之間添加和刪除角色。

    當使用以下操作管理本地策略的用戶和組角色時,可以使用-n選項指定項目。如果沒有指定,則使用當前項目。

    常見管理本地策略操作:





























    命令 描述
    oc adm policy who-can verb resource 設置哪些用戶可以對資源執行操作
    oc adm policy add-role-to-user role username 將指定角色綁定到指定用戶
    oc adm policy remove-role-from-user role username 從指定用戶中移除給定角色
    oc adm policy remove-user username 刪除指定的用戶及其所有角色
    oc adm policy add-role-to-group role groupname 將指定的角色綁定到指定的組
    oc adm policy remove-role-fromgroup role groupname 從指定組中移除給定角色
    oc adm policy remove-group groupname 刪除指定的組及其所有角色


    還可以使用如下所示的的操作管理cluster policy的role binding,這類命令不需要-n選項,因為cluster policy不在namespace級別上操作。

    常見管理cluster policy操作:




















    命令 描述
    oc adm policy add-cluster-role-to-user role username 將集群中所有項目的指定角色綁定到指定用戶
    oc adm policy remove-cluster-role-from-user role username 為集群中的所有項目從指定用戶中刪除指定角色
    oc adm policy add-cluster-role-togroup role groupname 為集群中的所有項目將指定的角色綁定到指定的組
    oc adm policy remove-cluster-role-from-group role groupname 從集群中所有項目的指定組中移除給定角色


    提示:oc policy命令應用於當前項目,而oc adm policy命令應用於集群範圍的操作。

    示例:在example項目中為developer用戶提供admin角色。

    [root@demo ~]# oc adm policy add-role-to-user admin developer -n example

    [root@demo ~]# oc describe policybindings :default -n example #檢查綁定

    三 安全上下文約束(SCCS)

    3.1 SCCS概述


    OpenShift提供安全上下文約束(SCCS),它控制pod可以執行的操作和它可以訪問的資源。默認情況下,任何容器的執行都只授予受限制的SCC定義的功能。

    SCCS相關命令:

      1 [user@demo ~]$ oc get scc			                        #列出可用的SCC
      2 [user@demo ~]$ oc describe scc scc_name		                #現實特定SCC詳細信息
      3 [user@demo ~]$ oc adm policy add-scc-to-user scc_name user_name
      4 [user@demo ~]$ oc adm policy add-scc-to-group scc_name group_name	#要授予用戶或組特定的SCC
      5 [user@demo ~]$ oc adm policy remove-scc-from-user scc_name user_name
      6 [user@demo ~]$ oc adm policy remove-scc-from-group scc_name group_name	#從特定的SCC中刪除用戶或組


    四 服務賬戶

    4.1 服務賬戶


    service account提供了一種靈活的方法來控制API訪問,而無需共享常規用戶的憑據。如果應用程序需要訪問受限制的SCC未授予的功能,可創建一個新的、特定的service account並將其添加到適當的SCC中。

    例如,在缺省情況下,OpenShift不支持部署需要提升特權的應用程序。若有此需求,可創建一個service account,修改dc,然後添加service account至SCC。

    示例:將anyuid配置為在容器中作為root用戶運行。

    [user@demo ~]$ oc create serviceaccount useroot #創建一個名為useroot的新服務帳戶

    [user@demo ~]$ oc patch dc/demo-app \

    –patch ‘{“spec”:{“template”:{“spec”:{“serviceAccountName”: “useroot”}}}}’ #修改應用程序的DC

    [user@demo ~]$ oc adm policy add-scc-to-user anyuid -z useroot #將useroot服務帳戶添加到anyuid SCC中,作為容器中的根用戶運行

    4.2 Web管理user成員


    OCP平台的默認配置是,在用戶首次登錄成功時,自動創建該用戶對象。

    要管理允許訪問項目的用戶,請以項目管理員或集群管理員的身份登錄到web控制台,並選擇要管理的項目。在左側窗格中,單擊Resources——>membership進入項目member頁面。

    在Users列中,在突出显示的文本框中輸入用戶名。在“添加另一個角色”列中,從用戶所在行的列表中選擇一個角色,然後單擊“添加”。

    4.3 Cli管理user成員


    CLI中如果自動創建對象功能被關閉,集群管理員可通過如下方式創建新用戶:

    [root@master ~]$ oc create user demo-user

    同時還需要在身份認證軟件中創建用戶,如為HTPasswdIdentityProvider才用戶命令如下:

    [root@master ~]$ htpasswd /etc/origin/openshift-passwd demo-user

    要向用戶添加項目角色,首先使用oc project命令輸入項目,然後使用oc policy add-role-to-user命令:

    [root@master ~]$ oc policy add-role-to-user edit demo-user

    要從用戶中刪除項目角色,使用oc policy remove-role-from-user命令:

    [root@master ~]$ oc policy remove-role-from-user edit demo-user

    並不是所有OpenShift角色都由項目限定範圍。要分配這些規則,請使用oc adm policy command命令。

    [root@master ~]$ oc adm policy add-cluster-role-to-user cluster-admin admin

    4.4 身份驗證和授權


    身份驗證層標識與對OpenShift容器平台API的請求相關聯的用戶,然後授權層使用關於請求用戶的身份信息來確定是否應該允許該請求。

    • user和group


    OCP容器平台中的用戶是一個可以向OpenShift API發出請求的實體。通常,這表示與OpenShift交互的develop或administrator的帳戶。

    可以將用戶分配給一個或多個組,每個組表示一組特定的角色(或權限)。當需要通過管理授權策略給多個客戶授權時候,group會比較合適。例如允許訪問項目中的對象,而不是單獨授予用戶。

    • Authentication Tokens


    API調用必須使用訪問令牌或X.509證書進行身份驗證,會話token表示用戶,並且是短期的,默認情況下在24小時內到期。

    可以通過運行oc whoami命令來驗證經過身份驗證的用戶。

    [root@master ~]$ oc login -u demo-user

    [root@master ~]$ oc whoami

    demo-user

    4.5 身份驗證類型


    本環境中,身份驗證由HTPasswdIdentityProvider模塊提供,該模塊根據使用htpasswd命令生成的文件驗證用戶名和密碼。

    OpenShift容器平台支持的其他認證類型包括:

    • Basic Authentication (Remote)


    一種通用的後端集成機制,允許用戶使用針對遠程標識提供者驗證的憑據登錄到OpenShift容器平台。用戶將他們的用戶名和密碼發送到OpenShift容器平台,OpenShift平台通過到服務器的請求驗證這些憑據,並將憑據作為基本的Auth頭傳遞。這要求用戶在登錄過程中向OpenShift容器平台輸入他們的憑據。

    • Request Header Authentication


    用戶使用請求頭值(如X-RemoteUser)登錄到OpenShift容器平台。它通常與身份驗證代理結合使用,身份驗證代理對用戶進行身份驗證,然後通過請求頭值為OpenShift容器平台提供用戶標識。

    • Keystone Authentication


    Keystone是一個OpenStack項目,提供標識、令牌、目錄和策略服務。OpenShift容器平台與Keystone集成,通過配置OpenStack Keystone v3服務器將用戶存儲在內部數據庫中,從而支持共享身份驗證。這種配置允許用戶使用Keystone憑證登錄OpenShift容器平台。

    • LDAP Authentication


    用戶使用他們的LDAP憑證登錄到OpenShift容器平台。在身份驗證期間,LDAP目錄將搜索與提供的用戶名匹配的條目。如果找到匹配項,則嘗試使用條目的專有名稱(DN)和提供的密碼進行簡單綁定。

    • GitHub Authentication


    GitHub使用OAuth,它允許與OpenShift容器平台集成使用OAuth身份驗證來促進令牌交換流。這允許用戶使用他們的GitHub憑證登錄到OpenShift容器平台。為了防止使用GitHub用戶id的未授權用戶登錄到OpenShift容器平台集群,可以將訪問權限限制在特定的GitHub組織中。

    五 管理項目及賬戶

    5.1 前置準備


    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    5.2 本練習準備


    [student@workstation ~]$ lab secure-resources setup

    5.3 創建htpasswd賬戶

      1 [kiosk@foundation0 ~]$ ssh root@master
      2 [root@master ~]# htpasswd -b /etc/origin/master/htpasswd user1 redhat
      3 [root@master ~]# htpasswd -b /etc/origin/master/htpasswd user2 redhat
      4 #添加基於htpasswd形式的user1和user2,密碼都為redhat。


    5.4 設置策略

      1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用管理員登錄
      2 [student@workstation ~]$ oc adm policy remove-cluster-role-from-group \
      3 self-provisioner system:authenticated:oauth
      4 #刪除所有賦予普通創建項目的功能,該命令可參考本環境如下目錄中的命令。
      5 [student@workstation ~]$ cat /home/student/DO280/labs/secure-resources/configure-policy.sh
      6 #!/bin/bash
      7 oc adm policy remove-cluster-role-from-group \
      8     self-provisioner system:authenticated system:authenticated:oauth


    5.5 驗證策略

      1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用普通用戶user1登錄
      2 [student@workstation ~]$ oc new-project test					#測試創建project
      3 Error from server (Forbidden): You may not request a new project via this API.


    5.6 創建項目

      1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用集群管理員登錄
      2 [student@workstation ~]$ oc new-project project-user1				#創建兩個項目
      3 [student@workstation ~]$ oc new-project project-user2


    5.7 將項目與user關聯

      1 #選擇項目1
      2 Now using project "project-user1" on server "https://master.lab.example.com:443".
      3 [student@workstation ~]$ oc policy add-role-to-user admin user1		#將user1添加為項目1的管理員
      4 role "admin" added: "user1"
      5 [student@workstation ~]$ oc policy add-role-to-user edit user2		#將user2添加為項目1的開發員
      6 role "edit" added: "user2"
      7 
      8 [student@workstation ~]$ oc project project-user2			        #選擇項目2
      9 Now using project "project-user2" on server "https://master.lab.example.com:443".
     10 [student@workstation ~]$ oc policy add-role-to-user edit user2		#將user2添加為項目2的開發員
     11 role "edit" added: "user2"


    5.8 驗證訪問

      1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用user1登錄
      2 [student@workstation ~]$ oc project project-user1					#驗證項目1的訪問
      3 Already on project "project-user1" on server "https://master.lab.example.com:443".
      4 [student@workstation ~]$ oc project project-user2					#驗證項目2的訪問
      5 error: You are not a member of project "project-user2".
      6 You have one project on this server: project-user1
      7 
      8 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com	#使用user2登錄
      9 [student@workstation ~]$ oc project project-user1
     10 Already on project "project-user1" on server "https://master.lab.example.com:443".	#驗證項目1的訪問
     11 [student@workstation ~]$ oc project project-user2
     12 Now using project "project-user2" on server "https://master.lab.example.com:443".	#驗證項目2的訪問


    5.9 部署特權應用

      1 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com
      2 [student@workstation ~]$ oc project project-user1
      3 Now using project "project-user1" on server "https://master.lab.example.com:443".
      4 [student@workstation ~]$ oc new-app --name=nginx --docker-image=registry.lab.example.com/nginx:latest
      5 #使用在項目1上不具備admin權限的用戶user2登錄,並部署應用,會出現如下提示:





    5.10 驗證部署

      1 [student@workstation ~]$ oc get pods




    結論:由上可知,部署失敗是因為容器映像需要root用戶,pod以CrashLoopBackOff或錯誤狀態結束。

    5.11 故障排除


    若要解決此故障需要減少特定項目的安全限制。

    要使用特權訪問運行容器,可創建一個允許pod使用操作系統普通用戶運行的service account。

    如下部分需要具有項目管理員特權的用戶執行,而另一些操作需要具有集群管理員特權的用戶執行。

    本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的configure-sc.sh腳本運行或複製。

      1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用項目1的admin賬戶登錄 
      2 [student@workstation ~]$ oc create serviceaccount useroot		  #創建服務賬戶
      3 serviceaccount "useroot" created
      4 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用集群管理員登錄
      5 [student@workstation ~]$ oc project project-user1			  #選擇項目1
      6 Already on project "project-user1" on server "https://master.lab.example.com:443".
      7 [student@workstation ~]$ oc adm policy add-scc-to-user anyuid -z useroot  #設置SCC策略
      8 scc "anyuid" added to: ["system:serviceaccount:project-user1:useroot"]    #將服務帳戶與anyuid安全上下文關聯,此操作需要集群管理員用戶。
      9 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com	#切換user2用戶
     10 [student@workstation ~]$ oc project project-user1
     11 Already on project "project-user1" on server "https://master.lab.example.com:443".
     12 [student@workstation ~]$ oc patch dc nginx --patch='{"spec":{"template":{"spec":{"serviceAccountName": "useroot"}}}}'



    #更新負責管理nginx的dc資源,任何開發人員用戶都可以執行此操作。本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的configure-sc.sh腳本運行或複製。

    5.12 驗證確認

      1 [student@workstation ~]$ oc get pods
      2 NAME            READY     STATUS    RESTARTS   AGE
      3 nginx-2-98k8f   1/1       Running   0          3m
      4 

    5.13 暴露服務

      1 [student@workstation ~]$ oc expose svc nginx
      2 route "nginx" exposed
      3 [student@workstation ~]$ oc get svc
      4 NAME      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
      5 nginx     ClusterIP   172.30.118.63   <none>        80/TCP    13m
      6 [student@workstation ~]$ oc get route
      7 NAME      HOST/PORT                                  PATH      SERVICES   PORT      TERMINATION   WILDCARD
      8 nginx     nginx-project-user1.apps.lab.example.com             nginx      80-tcp                  None


    5.14 測試訪問

      1 [student@workstation ~]$ curl -s http://nginx-project-user1.apps.lab.example.com

    5.15 策略刪除演示

      1 [student@workstation ~]$ oc login -u admin -p redhat
      2 [student@workstation ~]$ oc adm policy add-cluster-role-to-group self-provisioner system:authenticated system:authenticated:oauth
      3 cluster role "self-provisioner" added: ["system:authenticated" "system:authenticated:oauth"]
      4 [student@workstation ~]$ oc delete project project-user1
      5 project "project-user1" deleted
      6 [student@workstation ~]$ oc delete project project-user2
      7 [root@master ~]# htpasswd -D /etc/origin/master/htpasswd user1 
      8 [root@master ~]# htpasswd -D /etc/origin/master/htpasswd user2


    #為所有常規用戶重新啟用項目創建,即重置為初始狀態。本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的restore-policy.sh腳本運行或複製。

    六 管理加密信息

    6.1 secret特性


    Secret對象類型提供了一種機制來保存敏感信息,如密碼、OCP客戶端配置文件、Docker配置文件和私有倉庫憑據。Secrets將敏感內容與Pod解耦。可以使用Volume插件將Secrets掛載到容器上,或者系統可以使用Secrets代表pod執行操作。

    Secrets的主要特徵包括:

    • Secrets data可以獨立於其定義引用。
    • Secrets data Volume由臨時文件存儲支持。
    • 可以在名稱空間中共享Secrets data。

    6.2 創建Secrets


    在依賴於該Secrets的pod之前創建一個Secrets。

      1 [user@demo ~]$ oc create secret generic secret_name \
      2 --from-literal=key1=secret1 \
      3 --from-literal=key2=secret2	#用secret data創建secret對象
      4 [user@demo ~]$ oc secrets add --for=mount serviceaccount/serviceaccount-name \
      5 secret/secret_name		#更新pod的服務帳戶,允許引用該secrets。

    例如,允許一個運行在指定服務帳戶下的pod掛載一個secrets

    創建一個pod,該pod使用環境變量或數據卷作為文件的方式使用該secret,通常使用模板完成。

    6.3 使用secret暴露Pod


    secrets可以作為數據卷掛載,也可以作為環境變量以便供pod中的容器使用。

    例如,要向pod公開一個secrets,首先創建一個secrets並將username和password以k/v形式配置,然後將鍵名分配給pod的YAML文件env定義。

    示例:創建名為demo-secret的secrets,它定義用戶名和密碼為username/demo-user。

    [user@demo ~]$ oc create secret generic demo-secret \

    –from-literal=username=demo-user

    要使用前面的secret作為MySQL數據庫pod的數據庫管理員密碼,請定義環境變量,並引用secret名稱和密碼。

      1 env:
      2   - name: MYSQL_ROOT_PASSWORD
      3     valueFrom:
      4       secretKeyRef:
      5        key: username
      6        name: demo-secret


    6.4 web端管理secret


    從web控制台管理secret:

    1. 以授權用戶身份登錄到web控制台。

    2. 創建或選擇一個項目來承載secret。

    3. 導航到resource——>secrets。

    6.5 Secret使用場景


    • password和user names


    敏感信息(如password和user name)可以存儲在一個secret中,該secret被掛載為容器中的數據卷。數據显示為位於容器的數據卷目錄中的文件中的內容。然後,應用程序(如數據庫)可以使用這些secret對用戶進行身份驗證。

    • 傳輸層安全性(TLS)和密鑰對


    通過讓集群將簽名證書和密鑰對生成到項目名稱空間中的secret中,可以實現對服務的通信的保護。證書和密鑰對使用PEM格式存儲以類似tls.crt和tls.key的格式存儲在secret的pod中。

    七 ConfigMap對象

    7.1 ConfigMap概述


    ConfigMaps對象類似於secret,但其設計目的是支持處理不包含敏感信息的字符串。ConfigMap對象持有配置數據的鍵值對,這些配置數據可以在pods中使用,或者用於存儲系統組件(如控制器)的配置數據。

    ConfigMap對象提供了將配置數據注入容器的機制。ConfigMap存儲精細的粒度信息,比如單個屬性,或者詳細信息,比如整個配置文件或JSON blob。

    7.2 CLI創建ConfigMap


    可以使用–from-literal選項從CLI創建ConfigMap對象。

    示例:創建一個ConfigMap對象,該對象將IP地址172.20.30.40分配給名為serverAddress的ConfigMap密鑰。

      1 [user@demo ~]$ oc create configmap special-config \
      2 --from-literal=serverAddress=172.20.30.40
      3 [user@demo ~]$ oc get configmaps special-config -o yaml		#查看configMap
      4 apiVersion: v1
      5 data:
      6   key1: serverAddress=172.20.30.40
      7 kind: ConfigMap
      8 metadata:
      9   creationTimestamp: 2017-07-10T17:13:31Z
     10   name: special-config
     11 ……
     12 在配置映射的pod定義中填充環境變量APISERVER。
     13 env:
     14   - name: APISERVER
     15       valueFrom:
     16         configMapKeyRef:
     17           name: special-config
     18           key: serverAddress


    7.3 web管理ConfigMap


    從web控制台管理ConfigMap對象:

    1. 以授權用戶身份登錄到web控制台。

    2. 創建或選擇一個項目來承載ConfigMap。

    3. 導航到資源→配置映射。

    八 加密練習

    8.1 前置準備


    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    8.2 本練習準備


    [student@workstation ~]$ lab secure-secrets setup

    8.3 創建項目

      1 [student@workstation ~]$ oc login -u developer -p redhat
      2 [student@workstation ~]$ cd /home/student/DO280/labs/secure-secrets/
      3 [student@workstation secure-secrets]$ less mysql-ephemeral.yml		#導入本環境MySQL模板







    模板解讀:

    該mysql-ephemeral.yml模板文件,包含openshift項目中的mysql臨時模板,pod所需的其他環境變量由模板參數初始化,並具有默認值。

    但沒有secret定義,後續操作將手動創建模板所需的secret。

    根據模板的要求,創建一個包含MySQL容器image使用的憑證的secret,將這個secret命名為mysql。

    • 應用程序訪問的數據庫用戶名由database-user定義。
    • 數據庫用戶的密碼由database-password定義。
    • 數據庫管理員密碼由database-root-password定義

    8.4 創建新secret

      1 [student@workstation secure-secrets]$ oc create secret generic mysql \
      2 --from-literal='database-user'='mysql' \
      3 --from-literal='database-password'='redhat' \
      4 --from-literal='database-root-password'='do280-admin'
      5 [student@workstation secure-secrets]$ oc get secret mysql -o yaml	#確認secret




    8.5 創建應用

      1 [student@workstation secure-secrets]$ oc new-app --file=mysql-ephemeral.yml
      2 [student@workstation secure-secrets]$ oc get pods		#確認應用
      3 NAME            READY     STATUS    RESTARTS   AGE
      4 mysql-1-j4fnz   1/1       Running   0          1m


    8.6 端口轉發

      1 [student@workstation secure-secrets]$ cd
      2 [student@workstation ~]$ oc port-forward mysql-1-j4fnz 3306:3306



    提示:驗證完成之前forward不要關閉。

    8.7 確認驗證

      1 [student@workstation ~]$ mysql -uroot -pdo280-admin -h127.0.0.1	#新開終端測試MySQL



    九 管理security policy

    9.1 OCP authorization授權


    OCP定義了用戶可以執行的兩組主要操作:

    與項目相關的操作(也稱為本地策略):project-related

    與管理相關的操作(也稱為集群策略):administration-related

    由於這兩種策略都有大量可用的操作,所以將一些操作分組並定義為角色。














    默認角色 描述
    cluster-admin 此角色中的所有用戶都可以管理OpenShift集群。
    cluster-status 此角色中的所有用戶都提供對集群信息的只讀訪問。


    為管理本地政策,OCP提供以下角色:




















    默認角色 描述
    edit 角色中的用戶可以從項目中創建、更改和刪除公共應用程序資源,比如service和dc。 但是不能對限制範圍和配額等管理資源採取行動,也不能管理對項目的訪問權限。
    basic-user 角色中的用戶具有對項目的讀訪問權。
    self-provisioner 角色中的用戶可以創建新項目。這是一個集群角色,而不是項目角色。
    admin 角色中的用戶可以管理項目中的所有資源,包括授予對項目的其他用戶的訪問權


    除了能夠創建新應用程序之外,admin角色還允許用戶訪問項目資源,比如配額和限制範圍。

    edit角色允許用戶在項目中充當開發人員,但要在項目管理員配置的約束下工作。

    9.2 相關命令

      1 向集群用戶添加角色
      2 $ oc adm policy add-cluster-role-to-user cluster-role username
      3 示例:將普通用戶更改為集群管理員。
      4 $ oc adm policy add-cluster-role-to-user cluster-role username
      5 從用戶中刪除集群角色
      6 $ oc adm policy remove-cluster-role-from-user cluster-role username
      7 示例:將集群管理員更改為普通用戶。
      8 $ oc adm policy remove-cluster-role-from-user cluster-admin username
      9 將指定的用戶綁定到項目中的角色
     10 $ oc adm policy add-role-to-user role-name username -n project
     11 示例:在WordPress項目中dev用戶綁定basic-user角色。
     12 $ oc adm policy add-role-to-user basic-user dev -n wordpress


    9.3 權限及規則


    OpenShift將一組規則集合成一個角色,規則由謂詞和資源定義。如create user是OpenShift中的一條規則,它是一個名為cluster-admin的角色的所擁有的權限的一部分。

      1 $ oc adm policy who-can delete user

    9.4 user類型


    與OCP的交互基於用戶,OCP的user對象表示可以通過向該用戶或用戶組添加角色來從而實現相應權限的授予。

    Regular users:通常以這種用戶類型與OCP交互,常規用戶用User對象表。例如,user1,user2。

    System users:通常在安裝OCP中定義基礎設施時自動創建的,主要目的是使基礎設施能夠安全地與API交互。包括集群管理員(可以訪問所有內容)、每個節點的用戶、路由器和內部倉庫使用的用戶,以及各種其他用戶。還存在一個匿名系統用戶,默認情況下,該用戶用於未經身份驗證的請求。system user主要包括:system:admin、system:openshift-registry和system:node:node1.example.com。

    Service accounts:這些是與項目關聯的特殊系統用戶。有些是在第一次創建項目時自動創建的,項目管理員可以創建多個,以便定義對每個項目內容的訪問。Service accounts由ServiceAccount對象表示。Service accounts主要包括:system:serviceaccount:default:deployer和system:serviceaccount:foo:builder。

    每個用戶在訪問OpenShift容器平台之前必須進行身份驗證。沒有身份驗證或身份驗證無效的API請求將使用匿名系統用戶身份驗證來請求服務。身份驗證成功后,策略確定用戶被授權做什麼。

    9.5 安全上下文約束(SCCS)


    OpenShift提供了一種名為安全上下文約束的安全機制,它限制對資源的訪問,但不限制OpenShift中的操作。

    SCC限制從OpenShift中運行的pod到主機環境的訪問:

    • 運行特權容器
    • 請求容器的額外功能
    • 使用主機目錄作為卷
    • 更改容器的SELinux上下文
    • 更改用戶ID


    社區開發的一些容器可能需要放鬆安全上下文約束,因為它們可能需要訪問默認禁止的資源,例如文件系統、套接字或訪問SELinux上下文。

    OpenShift定義的安全上下文約束(SCCs)可以使用以下命令作為集群管理員列出。

    $ oc get scc

    SCC通常有以下7中SCCS:

    • anyuid
    • hostaccess
    • hostmount-anyuid
    • nonroot
    • privileged
    • restricted(默認)


    $ oc describe scc anyuid #查看某一種SCC詳情

    OpenShift創建的所有容器都使用restricted類型的SCC,它提供了對OpenShift外部資源的有限訪問。

    對於anyuid安全上下文,run as user策略被定義為RunAsAny,表示pod可以作為容器中可用的任何用戶ID運行。這允許需要特定用戶使用特定用戶ID運行命令的容器。

    要將容器更改為使用不同的SCC運行,需要創建綁定到pod的服務帳戶。

    $ oc create serviceaccount service-account-name #首先創建服務賬戶

    $ oc adm policy add-scc-to-user SCC -z service-account #將服務帳戶與SCC關聯

    要確定哪個帳戶可以創建需要更高安全性要求的pod,可以使用scc-subject-review子命令。

    $ oc export pod pod-name > output.yaml

    $ oc adm policy scc-subject-review -f output.yaml

    9.6 OpenShift與SELinux


    OpenShift要求在每個主機上啟用SELinux,以便使用強制訪問控制提供對資源的安全訪問。同樣,由OpenShift管理的Docker容器需要管理SELinux上下文,以避免兼容性問題。

    為了最小化在不支持SELinux的情況下運行容器的風險,可以創建SELinux上下文策略。

    為了更新SELinux上下文,可以使用現有的SCC作為起點生成一個新的SCC。

    $ oc export scc restricted > custom_selinux.yml #導出默認的SCC

    編輯導出的YAML文件以更改SCC名稱和SELinux上下文。

    $ oc create -f yaml_file #使用修改后的ymal重新創建一個SCC

    9.7 特權容器


    有些容器可能需要訪問主機的運行時環境。S2I構建器容器需要訪問宿主docker守護進程來構建和運行容器。

    例如,S2I構建器容器是一類特權容器,它要求訪問超出其自身容器的限制。這些容器可能會帶來安全風險,因為它們可以使用OpenShift節點上的任何資源。通過創建具有特權訪問權的服務帳戶,可以使用SCCs啟用特權容器的訪問。

    十 資源訪問控制綜合實驗

    10.1 前置準備


    準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

    10.2 本練習準備

      1 [student@workstation ~]$ lab secure-review setup

    10.3 創建用戶

      1 [root@master ~]# htpasswd /etc/origin/master/htpasswd user-review
      2 New password: 【redhat】
      3 Re-type new password: 【redhat】


    10.4 修改策略

      1 [student@workstation ~]$ oc login -u admin -p redhat
      2 [student@workstation ~]$ oc adm policy remove-cluster-role-from-group \
      3 self-provisioner system:authenticated system:authenticated:oauth
      4 禁用所有常規用戶的項目創建功能


    10.5 確認驗證

      1 [student@workstation ~]$ oc login -u user-review -p redhat
      2 [student@workstation ~]$ oc new-project test			#普通用戶無法創建項目
      3 Error from server (Forbidden): You may not request a new project via this API.


    10.6 創建項目

      1 [student@workstation ~]$ oc login -u admin -p redhat
      2 [student@workstation ~]$ oc new-project secure-review		#使用管理員創建項目


    10.7 授權用戶

      1 [student@workstation ~]$ oc project secure-review
      2 Already on project "secure-review" on server "https://master.lab.example.com:443".
      3 [student@workstation ~]$ oc policy add-role-to-user edit user-review	#將edit的role和user-review進行關聯


    10.8 測試訪問

      1 [student@workstation ~]$ oc login -u user-review -p redhat
      2 [student@workstation ~]$ oc project secure-review		#測試訪問
      3 Already on project "secure-review" on server "https://master.lab.example.com:443".


    10.9 檢查模板

      1 [student@workstation ~]$ cd /home/student/DO280/labs/secure-review/
      2 [student@workstation secure-review]$ less mysql-ephemeral.yml





    模板解讀:

    該mysql-ephemeral.yml模板文件,包含openshift項目中的mysql臨時模板,pod所需的其他環境變量由模板參數初始化,並具有默認值。

    但沒有secret定義,後續操作將手動創建模板所需的secret。

    根據模板的要求,創建一個包含MySQL容器image使用的憑證的secret,將這個secret命名為mysql。

    • 應用程序訪問的數據庫用戶名由database-user定義。
    • 數據庫用戶的密碼由database-password定義。
    • 數據庫管理員密碼由database-root-password定義


    使用user-review developer用戶創建一個名為mysql的secret。這個secret應該存儲用戶名mysql、密碼redhat和數據庫管理員密碼do280-admin。

    數據庫用戶名由database-user定義。此用戶的密碼由mysql secret密鑰定義。

    數據庫管理員密碼由database-root-password定義。

    10.10 創建secret

      1 [student@workstation secure-review]$ oc create secret generic mysql \
      2 --from-literal='database-user'='mysql' \
      3 --from-literal='database-password'='redhat' \
      4 --from-literal='database-root-password'='do280-admin'
      5 [student@workstation secure-review]$ oc get secret mysql -o yaml	#確認驗證secret


    10.11 部署應用

      1 [student@workstation secure-review]$ oc new-app --file=mysql-ephemeral.yml
      2 [student@workstation secure-review]$ oc get pods
      3 NAME            READY     STATUS    RESTARTS   AGE
      4 mysql-1-2lr7t   1/1       Running   0          31s


    10.12 轉發端口

      1 [student@workstation ~]$ oc port-forward mysql-1-2lr7t 3306:3306

    10.13 測試訪問

      1 [student@workstation ~]$ mysql -umysql -predhat -h127.0.0.1

    10.14 部署phpmyadmin應用


    使用內部倉庫registry.lab.example.com的image部署phpmyadmin:4.7容器。phpmyadmin:4.7容器需要名為PMA_HOST的環境變量來提供MySQL服務器的IP地址。

    使用模板創建一個基於FQND的MySQL pod的service。

    為使用模板創建的MySQL服務器pod使用服務FQDN,該模板是mysql.secure-review.svc.cluster.local。

      1 [student@workstation ~]$ oc new-app --name=phpmyadmin \
      2 --docker-image=registry.lab.example.com/phpmyadmin/phpmyadmin:4.7 \
      3 -e PMA_HOST=mysql.secure-review.svc.cluster.local






    結論:該命令會發出警告,提示需要root特權。默認情況下,OpenShift不支持使用操作系統的root用戶運行容器。

    10.15 查看pod

      1 [student@workstation ~]$ oc get pods
      2 NAME                 READY     STATUS    RESTARTS   AGE
      3 mysql-1-2lr7t        1/1       Running   0          8m
      4 phpmyadmin-1-v7tl7   0/1       Error     2          1m
      5 因為沒有root權限,因此部署失敗,需要提權。


    10.16 授予權限

      1 [student@workstation ~]$ oc login -u admin -p redhat			#使用管理員登錄
      2 [student@workstation ~]$ oc create serviceaccount phpmyadmin-account	#首先創建服務賬戶
      3 [student@workstation ~]$ oc adm policy add-scc-to-user anyuid -z phpmyadmin-account
      4 scc "anyuid" added to: ["system:serviceaccount:secure-review:phpmyadmin-account"]	#將服務帳戶與anyuid安全上下文關聯


    10.17 更新應用

      1 [student@workstation ~]$ oc patch dc phpmyadmin --patch='{"spec":{"template":{"spec":{"serviceAccountName": "phpmyadmin-account"}}}}'


    #更新負責管理phpmyadmin的dc資源,任何開發人員用戶都可以執行此操作。

    本環境中,相關操作命令可以從/home/student/DO280/labs/secure-review文件夾中的patch-dc.sh腳本運行或複製。

    10.18 確認驗證

      1 [student@workstation ~]$ oc login -u user-review -p redhat
      2 [student@workstation ~]$ oc get pods			#確認pod是否正常
      3 NAME                 READY     STATUS    RESTARTS   AGE
      4 mysql-1-2lr7t        1/1       Running   0          13m
      5 phpmyadmin-2-bdjvq   1/1       Running   0          1m


    10.19 暴露服務

      1 [student@workstation ~]$ oc expose svc phpmyadmin --hostname=phpmyadmin.apps.lab.example.com

    10.20 訪問測試

      1 [student@workstation ~]$ curl -s http://phpmyadmin.apps.lab.example.com

    10.21 確認及刪除

      1 [student@workstation ~]$ lab secure-review grade		#環境腳本判斷
      2 [student@workstation ~]$ oc login -u admin -p redhat
      3 [student@workstation ~]$ oc adm policy add-cluster-role-to-group \
      4 self-provisioner system:authenticated system:authenticated:oauth
      5 [student@workstation ~]$ oc delete project secure-review
      6 [student@workstation ~]$ ssh root@master htpasswd -D \
      7 /etc/origin/master/htpasswd user-review			#刪除用戶
      8 [student@workstation ~]$ oc delete user user-review		#刪除項目

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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