標籤: 台北網頁設計

  • Tesla擴大充電網絡規模,供卓越充電體驗

    Tesla擴大充電網絡規模,供卓越充電體驗

    隨著Tesla 準備生產首款因應大眾市場的車型,Model S、Model X 的產量持續擴大,為客戶提供卓越的充電體驗成為Tesla 的首要目標。為現在和未來的車主提供便捷、豐富、可靠的充電服務對我們以及公司的使命至關重要。2017 年,Tesla 全球充電網絡規模將擴大一倍,並對現有的充電站進行擴建, 讓駕駛不必排隊等候充電;同時,我們還將擴大市中心區域的充電站 覆蓋。

    一直以來,最便捷的充電方式都是夜間在停車位充電。為了更好的服務出門在外以及缺乏穩定家用充電設備的車主,我們將繼續大舉擴建Tesla 公共充電網絡。自2012 年Tesla 超級充電站網絡投入使用至今,Tesla 已經建造了5,400 多個超級充電座, 目的是讓全球20 多萬Tesla 車主都能方便地進行長途旅行。此外,我們還建立了一個已擁有9,000 多個目的地充電座的充電網絡,通過在酒店、度假村、餐廳等場所安裝Tesla 壁掛式充電座,為車主提供如同在家一般的便捷舒適的充電體驗。但我們知道, 要想真正推動電動車的普及,我們必須繼續投資建設充電基礎設施。

    2017 年初,全球已經有5,000 多個Tesla 超級充電座;截至今年年底,Tesla 將把這個數量擴大一倍,超級充電座總數將突破1 萬個,目的地充電座總數增至約1.5 萬個。北美地區的超級充電座數量將增至現有的1.5 倍,僅加州就將增設超過1,000 個超級充電座。選址工作已經開始,新的充電站很快將投入建設,並於今年夏天旅行季節到來之前投入使用。

    為此,我們將沿著最繁忙的行駛路線建設規模更大的充電站,這些道路沿線將同時出現數十座Tesla 超級充電站。此外,我們還將在距離公路較遠的地帶增設多個充電站,讓當地Tesla 駕駛可以隨需隨充,從而實現市中心隨處皆可充電的目標。

    憑藉全球領先、便捷的充電技術,不斷打造獨一無二、充分利用此充電技術的電動車,Tesla 長期以來一直引領全球電動車產業的發展。充電網絡的不斷擴展將確保Tesla 駕駛在任何情況下都能快速、便捷地為車輛充電,而這正是Tesla 工作的首要目標。

    針對台灣,目前已完成架構超過160 座目的地充電站,以及位於台北花博的6 座超級充電站。Tesla 預計於2017 年中完成超過200 座的目的地充電站,分佈於全台超過50 個地點。同時,車主高度期待位於中部、南部的超級充電站也預計同樣於年中完成建置。如此一來,Tesla 車主即可毫無顧慮的完成全島南北往返的長途旅行。針對如此的長途旅行,不僅是都會區,Tesla 也在各個熱門景點附近的飯店設立目的地充電站,以便車主旅途中隔夜充電使用。

    (合作媒體:。圖片出處:Steve Jurvetson via Flickr CC2.0)  

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

  • 台達電佈局電動車產品,拓歐美電動車市場

    台達電佈局電動車產品,拓歐美電動車市場

    台達電於4月28日舉行法說會,公佈2017年第一季財報合併營收489.25億元新台幣,季減14%,年增2.8%;毛利率27.2%,季減0.06個百分點,年增0.26個百分點;因管控得宜,第一季匯兌收益1.4億元;稅後淨利39.19億元,每股盈餘1.51元,低於前一季的1.91元,略高於去年同期的1.50元。

    台達電執行長鄭平表示,公司5月起進行組織調整,擴大在電動車相關產品佈局,目前公司已打入歐美電動車廠,提供包括車載充電器、動力馬達、DC-DC轉換器等電動車零組件;在中國大陸也打入合資車廠,雖然電動車領域營收短期內成長幅度不大,但仍看好電動車領域未來長期的布局效益。

    台達電宣布組織調整,自2日起,將以「電源及零組件」、「自動化」與「基礎設施」為新三大業務範疇,其中,電源及零組件業務包括電動車方案事業群(EVSBG)、嵌入式電源系統事業群(EPSBG)、商用電源事業群(MPBG)、零組件事業群(CPBG)、風扇暨熱傳導事業群(FMBG);自動化業務包括機電事業群(IABG)、樓宇自動化事業群(BABG);至於基礎設施業務則包括資通訊基礎設施事業群(ICTBG)、能源基礎設施事業群(EISBG)。以新三大業務範疇區分,第一季電源及零組件占營收比重為55%、自動化占比為11%,基礎設施則占31%。

    對於未來營運展望,台達電董事長海英俊表示,第二季看好IA、樓宇自動化、數據中心的營運動能;今年雖然有匯率變數,但強調公司營運不會受到影響,並將持續朝向高毛利產品發展,讓毛利率能維持目前水準或更好。

    (本文內容由授權使用)  

     

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • 地球發燒了研究: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行銷專家,教你從零開始的技巧