多線程編程(3)——synchronized原理以及使用

一、對象頭

  通常在java中一個對象主要包含三部分:

  • 對象頭 主要包含GC的狀態、、類型、類的模板信息(地址)、synchronization狀態等,在後面介紹。

  • 實例數據:程序代碼中定義的各種類型的字段內容。

  • 對齊數據:對象的大小必須是 8 字節的整數倍,此項根據情況而定,若對象頭和實例數據大小正好是8的倍數,則不需要對齊數據,否則大小就是8的差數。

先看下面的實例、程序的輸出以及解釋。

/*需提前引入jar包
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core 解析java對象布局 -->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
​
*/
//Java對象以8個字節對其,不夠則使用對其數據
public class Student {
    private int id;       // 4字節
    private boolean sex;  // 1字節
    public Student(int id, boolean sex){
        this.id = id;
        this.sex = sex;
    }
}
public class Test01 {
    public static void main(String[] args) {
        Student stu = new Student(6, true);
        //計算對象hash,底層是C++實現,不需要java去獲取,如果此處不調用,則後面的hash值不會去計算
        System.out.println("hashcode: " + stu.hashCode());  
        System.out.println(ClassLayout.parseInstance(stu).toPrintable());
    }
}
/* output
hashcode: 523429237
com.thread.synchronizeDemo.Student object internals:
OFFSET SIZE TYPE DESCRIPTION        VALUE
 0     4    (object header)    01 75 e5 32 (00000001 01110101 11100101 00110010) (853898497)
4      4     (object header)    1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
8      4     (object header)    43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12     4       int Student.id                                6
16     1   boolean Student.sex                               true
17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
​
備註:上述代碼在64位的機器上運行,此時
對象頭占  (4+4+4)*8 = 96 位(bit)
實例數據  (4+1)*8 = 40 位(bit)
對齊數據  7*8 = 56 位(bit) 因為Java對象以8個字節對其的方式,需補7byte去對齊
*/

   下面主要陳述對對象頭的解釋,內容從hotspot官網摘抄下來的信息:

object header

  Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

mark word

  The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

klass pointer

  The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original objec

  由此可知,對象頭主要包含GC的狀態(用4位表示——表示範圍0-15,用來記錄GC年齡,這也就是為什麼對象在survivor中從from區到to區來迴轉換15次後轉入到老年代tenured區)、類型、類的模板信息(地址)、synchronization 狀態等,由兩個字組成mark word和klass pointer(類元素據信息地址,具體數據通常在堆的方法區中,即8字節,但有時候會有一些優化設置,會開啟指針壓縮,將代表klass pointer的8字節變成4字節大小,這也是為什麼在上述代碼中對象頭大小是(8+4)byte,而不是16byte。)。本節最主要介紹對象頭的mark word這部分。關於對象頭中每部分bit所代表的意義可以查看hotspot源碼中代碼的注,這段註釋是從openjdk中拷貝的。

JVM和hotspot、openjdk的區別

JVM是一種產品的規範定義,hotspot(Oracle公司)是對該規範實現的產品,還有遵循這些規範的其他產品,比如J9(IBM開發的一個高度模塊化的JVM)、Zing VM等。

openjdk是一個hotspot項目的大部分源代碼(可以通過編譯后變成.exe文件),hotspot小部分代碼Oracle並未公布

// openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\oops\markOop.hpp
/*
Bit-format of an object header (most significant first, big endian layout below):
​
32 bits:
--------
        hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
        JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
        size:32 ------------------------------------------>| (CMS free block)
        PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
​
64 bits:
--------
unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
size:64 ----------------------------------------------------->| (CMS free block)
​
*/

可以看到在32位機器和64位機器中,對象的布局的差異還是很大的,本文主要 敘述64位機器下的布局,其實兩者無非是位數不同而已,大同小異。在64位機器用64位(8byte)表示Mark Word,首先前25位(0-25)是未被使用,接下來31位表示hash值,然後是對象分代年齡大小,最後Synchronized的鎖信息,分為兩部分,共3bit,如下錶,鎖的嚴格性依次是鎖、偏向鎖、輕量級鎖、重量級鎖。

 

關於鎖的一些解釋

無鎖

  無鎖沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功。

偏向鎖

  引入偏向鎖是為了在無多線程競爭的情況下,一段同步代碼一直被一個線程所訪問因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令,當由另外的線程所訪問,偏向鎖就會升級為輕量級鎖。

輕量級鎖 

  當鎖是偏向鎖的時候,被另外的線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,從而提高性能。輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。

重量級鎖

  依賴於操作系統Mutex Lock所實現的鎖,JDK中對Synchronized做的種種優化,其核心都是為了減少這種重量級鎖的使用。JDK1.6以後,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了“輕量級鎖”和“偏向鎖”。  

GC

  這並不是鎖的狀態,而是GC標誌,等待GC回收。

現在開始從程序層面分析前面程序的對象頭的布局信息,在此之前需要知道的是,在windows中對於數據的存儲採用的是小端存儲,所以要反過來讀

大端模式——是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中,這樣的存儲模式有點兒類似於把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;這和我們的閱讀習慣一致。

小端模式——是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低。 一般在網絡中用的大端;本地用的小端;

運行程序如下,可以看到對應的hashcode值被打印出來:

public static void main(String[] args) {
     Student stu = new Student(6, true);
    //Integer.toHexString()此方法返回的字符串表示的無符號整數參數所表示的值以十六進制
     System.out.println("hashcode: " + Integer.toHexString(stu.hashCode()));
     System.out.println(ClassLayout.parseInstance(stu).toPrintable());
}
/*
hashcode: 1f32e575
com.thread.synchronizeDemo.Student object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 75 e5 32 (00000001 01110101 11100101 00110010) (853898497)
      4     4           (object header)                           1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
      8     4           (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4       int Student.id                                6
     16     1   boolean Student.sex                               true
     17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

//前8個字節反過來看,可以看出對象頭的hash是1f32e575,同時是無鎖的狀態00000001
*/

 二、Monitor

       可以把它理解為一個同步工具(數據結構),也可以描述為一種同步機制,通常被描述為一個對象。每個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關係有存在多種實現方式,如monitor可以與對象一起創建銷毀或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有后,它便處於鎖定狀態(每一個線程都有一個可用 monitor record 列表)[具體可以看參考資料5]。需要注意的是這種監視器鎖是發生在對象的內部鎖已經變成重量級鎖的時候。

/*  openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\runtime\ObjectMonitor.hpp
// initialize the monitor, exception the semaphore, all other fields // are simple integers or pointers ObjectMonitor() { _header = NULL; _count = 0; //記錄個數 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; //處於wait狀態的線程,會被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //處於等待鎖block狀態的線程,會被加入到該列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; } */

  Monitor的實現主要藉助三個結構去完成多線程的併發操作——_owner、_WaitSet 、_EntryList。當多個線程同時訪問由synchronized修飾的對象、類或一段同步代碼時,首先會進入_EntryList 集合,如果某個線程取得了_owner的所有權,該線程就可以去執行,如果該線程調用了wait()方法,就會放棄_owner的所有權,進入等待狀態,等下一次喚醒。如下圖(圖片摘自參考資料5)。

 三、synchronized的用法

     synchronized修飾方法和修飾一個代碼塊類似,只是作用範圍不一樣,修飾代碼塊是大括號括起來的範圍,而修飾方法範圍是整個函數。其中synchronized(this) 與synchronized(class) 之間的區別有以下五點要注意:

    1、對於靜態方法,由於此時對象還未生成,所以只能採用類鎖;

    2、只要採用類鎖,就會攔截所有線程,只能讓一個線程訪問。

    3、對於對象鎖(this),如果是同一個實例,就會按順序訪問,但是如果是不同實例,就可以同時訪問。

   4、如果對象鎖跟訪問的對象沒有關係,那麼就會都同時訪問。

   5、當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

當然,Synchronized也可修飾一個靜態方法,而靜態方法是屬於類的而不屬於對象的,所以synchronized修飾的靜態方法鎖定的是這個類的所有對象。關於如下synchronized的用法,我們經常會碰到的案例:

public class Thread5 implements Runnable {
    private static int count = 0;
    public synchronized static void add() {
        count++;
    }
    @Override
    public void run() {
        for (int i = 0; i < 1000000; i++) {
            synchronized (Thread5.class){
                count++;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 20; i++) {
            es.execute(new Thread5());
        }
        es.shutdown();
        es.awaitTermination(6, TimeUnit.SECONDS);
        System.out.println(count);
    }
}
/* 類鎖
  20000000
  */

而一旦換成對象鎖,不同實例,就可以同時訪問。則會出錯:

public void run() {
        for (int i = 0; i < 1000000; i++) {
            synchronized (this){
                count++;
            }
        }
}
/* 對象鎖
 10746948
*/

這是因為靜態變量並不屬於某個實例對象,而是屬於類所有,所以對某個實例加鎖,並不會改變count變量臟讀和臟寫的情況,還是造成結果不正確。

 

參考資料

  1. 對象布局的各部分介紹——

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

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

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

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

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

小程序雲開發:菜鳥也能全棧做產品

我想獨立實現一個全棧產品為什麼這麼難

日常生活中,我們會使用很多軟件產品。在使用這些產品的時候,我們看得見的東西稱為“前端界面”如一個輸入框、一個按鈕,點擊按鈕之後發生的一切看不見的東西稱為“後端服務”。與之對應的創造者分別稱為“前端程序員”、“後端程序員”,然而,一個完整產品的開發不僅僅是只有前端和後端,還有設計師,架構師,運維等。有沒有可能這些所有的事情都一個人干呢?有可能,事實上如今就有很多的“全棧工程師”,他們身兼數職,是多面手。能獨立完成一個產品的方方面面。這種人固然十分了得,他們通常具有多年的經驗,涉獵廣泛,是老手,也是高手,當有一個產品想法的時候,他們可以用自己的全面專業技能,盡情的發揮去實現自己的想法。所以,從某種意義上講“全棧也是一種自由”,你可以自由的實現你的想法,這簡直太美妙了!

然而,很多時候當我們有一個產品想法的時候,我們往往發現,前端寫完了,後端怎麼搞?數據庫怎麼搞?域名怎麼搞?域名還要備案?應用部署怎麼搞?我的買什麼樣的服務器啊?靜態資源 CDN 怎麼搞?文件上傳服務器怎麼搞?萬一訪問用戶多了能撐住嗎?等等……問題很多,導致你的一個個想法,都只是在腦海中曇花一現,從來都無法將她們實現,或者說你激情飽滿的實現了其中自己最擅長的一部分,當碰到其他難題的時候就止步了。於是仰天長嘯:我就想獨立做一個完整的產品為什麼這麼難?年輕人,這一切都不怪你……

破局:小程序雲開發

為什麼使用小程序雲開發來破局?

為啥是用“小程序雲開發”來破局?首先,我們的目的是全棧實現一個產品。全棧可以有多種技術方案,你可用任何你能會的技能來達到全棧的目的。你可以開發安卓,IOS,或者 PC 站,然而小程序是最實際的!為啥?手機上能做的事情為啥要用 PC 版?OK,既然手機版比較好,那能不能再簡單一點?能,就是小程序,不需要開發IOS,安卓兩個版本。可以快速產出,快速試錯。

其次,前面說到了,全棧實現一個產品並不容易,對很多人來說甚至是巨難!選擇了小程序已經是比較划算的方案。而再集成雲開發,全棧立馬就有了。這就是為什麼選擇“小程序雲開發”來破局。

小程序雲開發是什麼?

小程序雲開發是什麼?官方文檔是這麼說的:開發者可以使用雲開發開發微信小程序、小遊戲,無需搭建服務器,即可使用雲端能力。雲開發為開發者提供完整的原生雲端支持和微信服務支持,弱化後端和運維概念,無需搭建服務器,使用平台提供的 API 進行核心業務開發,即可實現快速上線和迭代,同時這一能力,同開發者已經使用的雲服務相互兼容,並不互斥。

看完上面的描述,也許你仍然無法非常清楚的知道什麼是“小程序雲開發”,沒關係,你只需要注意加粗的部分,大概知道它“無需搭建服務器”,從傳統觀念將,這個似乎“毀三觀”咋可能沒服務器啊?是的,可以沒有傳統意義上的服務器,這種模式是 serveless 的。

那麼,小程序雲開發提供了哪些東西來破局呢?且看下面的表格:

能 力 作 用 說 明
雲函數 無需自建服務器 在雲端運行的代碼,微信私有協議天然鑒權,開發者只需編寫自身業務邏輯代碼
數據庫 無需自建數據庫 一個既可在小程序前端操作,也能在雲函數中讀寫的 JSON 數據庫
存儲 無需自建存儲和 CDN 在小程序前端直接上傳/下載雲端文件,在雲開發控制台可視化管理
雲調用 原生微信服務集成 基於雲函數免鑒權使用小程序開放接口的能力,包括服務端調用、獲取開放數據等能力

上面的表格中提到了“雲開發”中的一些能力:“雲函數”,“數據庫”,“存儲”,“雲調用”,我們可以將這些詞帶入你曾經開發過的應用,看看它們分別代表了哪些部分。對於程序員來說,如果有疑問的話,沒有什麼是一個 helloword 解決不了的。

實戰:獨立開發一個簡易的零售小程序

哆嗦再多,不如實戰。下面我們就來使用小程序雲開發實現一個簡單的零售小程序。

項目構思

既然是一個零售小程序,那麼我們可以思考一下零售小程序的大致業務流程,以及粗略的梳理一下,其功能點。現根據自己的想法,大致畫一下草圖,如果沒有靈感可以參考一下別的 APP 是如何設計的。

我根據自己的想法設計之後是這樣的:

功能模塊:首頁,商品列表頁,購物車,確認訂單,個人中心,個人訂單,管你模塊(商品添加,分類添加)其中商品需要上傳圖片。

梳理完功能之後,我們對於要實現的東西已經有個初步的概念了。接下來,我們需要大概畫一下頁面設計、及功能流轉。初次設計可能沒有太多經驗,沒關係,開始做就行了,做着做着就會想法越來越多,然後優化的越來越好。。我也是經過了多番修改調整,最終找到了一些思路。我的(拙劣)設計如下,圖片如果看不清楚可複製圖片鏈接在新窗口打開查看:

說明,以上圖片是根據成品(我真的開發了一個雲小程序並上線使用了)截圖的,而實際我再設計的時候也是經過幾番修改才最終定成這樣。

同時,補充說明一下,這裏前端頁面使用的是 vant-weapp控件,非常好用。推薦!如果你和我一樣是一個純後端程序員,建議使用 vant-weapp 來作為 ui,非常方便。否則自己寫頁面樣式的話可能就做不出來了。全棧不是那麼好乾的啊。選擇自己能駕馭的,能實現最終功能,就是一個合格的全棧。

創建小程序雲開發項目

我們先下載微信小程序開發工具,下載地址,安裝好了之後,新建項目,界面如下,APPID 需要你自己去註冊一個。然後注意,選擇“小程序雲開發”,如下圖所示:

創建好了之後,項目目錄如下,先看 1 標註的地方:

如果你曾經有過小程序的開發經驗,那麼miniprogram文件夾下面的結構你肯定熟悉了,miniprogram下面的子目錄分別是小程序對應的組件、圖片、頁面、樣式以及app.js,app.json,sitemap.json,其中components下面的vant-weapp就是上面提到的 ui 組件。

最後一個比較重要的文件夾就是cloudfunctions,這個目錄是用來存放“雲函數的”,雲函數就是我們的後端。每一個雲函數提供一個服務。一個個的雲函數組成了我們整體的後端服務。雲函數可以看做是 FaaS(function as a service)。途中,2 標記的位置的“雲開發”按鈕,我們點進去,就可以看到“雲開發的控制台”,如下圖所示:

如果上圖看不清楚,可以複製鏈接到新的瀏覽器窗口查看,如圖,小程序雲開發默認的免費套餐有一定的額度可供使用。首頁便是使用統計。然後我們能看到,有“數據庫”,“存儲”,“雲函數”。

這裏的“數據庫”其實就是類似於一個 MongoDB,你可以點進去創建一個個的 collection(即:關係型數據庫中的table);這裏的“存儲”其實就是“文件夾”,我們可以通過微信提供的 api把圖片上傳到“存儲”中;這裏的“雲函數”就是我們需要實現的後端業務邏輯,他就是一個個的函數(函數由我們自己寫好後上傳)。一般開發過程中我們在開發者工具中的cloudfunctions目錄下創建雲函數(比方說是:user-add)開發完成之後在雲函數目錄點擊右鍵——上傳即可。然後就可以在小程序的代碼中調用這個user-add雲函數。

雲開發之——3 分鐘實現文件上傳

注意:在開始雲開發之前,我們現在 小程序代碼的 app.js 中加入wx.cloud.init,如下:

App({
  onLaunch: function () {
    if (!wx.cloud) {
      console.error('請使用 2.2.3 或以上的基礎庫以使用雲能力')
    } else {
      wx.cloud.init({
        // env 參數說明:
        //   env 參數決定接下來小程序發起的雲開發調用(wx.cloud.xxx)會默認請求到哪個雲環境的資源
        //   此處請填入環境 ID, 環境 ID 可打開雲控制台查看
        //   如不填則使用默認環境(第一個創建的環境)
        env: 'your-env-id',
        traceUser: true,
      })
    }
    this.globalData = {}
  }
})

上面的圖中,我們已經看到了“商品添加”頁面的效果,它需要我們輸入商品名稱、價格、並上傳圖片,然後保存。傳統架構中,上傳圖片需要前端頁面擺一個控件,然後後端提供一個 api用來接收前端傳來的文件,通常來說這個後端 api 接收到圖片之後,會將圖片文件保存到自己的文件服務器或者是阿里雲存儲、或者是七牛雲存儲之類的。然後返回給你一個文件鏈接地址。非常麻煩,然而,小程序雲開發上傳文件超級簡單,上代碼:

頁面代碼:
<van-notice-bar
  scrollable="false"
  text="發布商品"
/>
  <van-field
    value="{{ productName }}"
    required
    clearable
    label="商品名稱"
    placeholder="請輸入商品名稱"
    bind:change="inputName"
  />
    <van-field
    value="{{ productPrice }}"
    required
    clearable
    label="價格"
    icon="question-o"
     bind:click-icon="onClickPhoneIcon"
    placeholder="請輸入價格"
    error-message="{{phoneerr}}"
    border="{{ false }}"
    bind:change="inputPrice"
  />

<van-action-sheet
  required
  show="{{ showSelect }}"
  actions="{{ actions }}"
  close-on-click-overlay="true"
  bind:close="toggleSelect"
  bind:select="onSelect" cancel-text="取消"
/>
  <van-field
    value="{{ productCategory }}"
    center
    readonly
    label="商品分類"
    border="{{ false }}"
    use-button-slot
  >
    <van-button slot="button" size="small" plain type="primary"  
     bind:click="toggleSelect">選擇分類</van-button>
  </van-field>
  
  <van-button class="rightside" type="default" bind:click="uploadImage" >上傳商品圖片</van-button>
  <view class="imagePreview">
    <image src="{{productImg}}" />
  </view>
 <van-submit-bar
  price="{{ totalShow }}"
  button-text="提交"
  bind:submit="onSubmit"
  tip="{{ false }}"
 >
 </van-submit-bar> 
<van-toast id="van-toast" />
<van-dialog id="van-dialog" />

這裡有個控件,綁定了uploadImage方法,其代碼為:

  uploadImage:function(){
    let that = this;
    wx.chooseImage({
      count: 1,
      sizeType: ['compressed'],
      sourceType: ['album', 'camera'],
      success(res) {
        wx.showLoading({
          title: '上傳中...',
        })
        const tempFilePath = res.tempFilePaths[0]
        const name = Math.random() * 1000000;
        const cloudPath = name + tempFilePath.match(/\.[^.]+?$/)[0]
        wx.cloud.uploadFile({
          cloudPath:cloudPath,//雲存儲圖片名字
          filePath: tempFilePath,//臨時路徑
          success: res => {
            let fileID = res.fileID;
            that.setData({
              productImg: res.fileID,
            });
            wx.showToast({
              title: '圖片上傳成功',
            })
          },
          fail: e =>{
            wx.showToast({
              title: '上傳失敗',
            })
          },
          complete:()=>{
            wx.hideLoading();
          }
        });
      }
    })
  }

這裏,wx.chooseImage用於調起手機選擇圖片(相冊/相機拍照),然後wx.cloud.uploadFile用於上傳圖片到上面說到的雲開發能力之一的“存儲”中。上傳圖片成功之後返回一個文件 ID,類似:

cloud://release-0kj63.7265-release-0kj63-1300431985/100477.13363146288.jpg  

這個鏈接可以直接在小程序頁面展示:

<image src="cloud://release-0kj63.7265-release-0kj63-1300431985/100477.13363146288.jpg  " />

也可以通過微信 api,裝換成 http 形式的圖片鏈接。

雲開發之——操作數據庫,1 分鐘寫完保存商品到數據庫的代碼

上面我們實現了商品圖片上傳,但是,商品圖片並沒有保存到數據庫。正常錄入商品的時候,我們會填好商品名稱,價格等,然後上傳圖片,最終點擊“保存”按鈕,將商品保存到數據庫。傳統模式下,前端仍然是需要調用一個後端接口,通過 post 提交數據,最終由後端服務(比如 java 服務)將數據保存到數據庫。小程序雲開發使得操作數據庫十分簡單,首先我們在雲開發控制台創建“商品表”,即一個 collection,取名為:products。然後我們就可以保存數據到數據庫了,代碼如下:

onSubmit:function(){
    // 校驗代碼,略
    let product = {};
    product.imgId = this.data.productImg;
    product.name= this.data.productName;
    product.categoryId = this.data.productCategoryId;
    product.price = this.data.productPrice;
    // 其他賦值,略
    const db = wx.cloud.database();
    db.collection('products').add({
     data: product,
     success(res) {
       wx.showToast({
         title: '保存成功',
       })
     }
   });
  }

以上就實現了數據入庫,就這點代碼,超簡單,1 分鐘寫完,誠不欺我。其中這裏的products就是我們的“商品表”,之前說過,類似 MongoDB 數據庫,這裏操作的是db.collection,這和 MongoDB 的語法差不多。

雲開發之——使用雲函數完成後端業務邏輯,訂單創建

小程序雲開發提供了幾大能力:“數據庫”,“存儲”,“雲函數”,前兩項我們已經有所體會了。下面我們能創建一個雲函數來實現訂單創建。這裏說明,雲函數其實就是 一段JavaScript 代碼,上傳至雲服務器之後,最終也是運行在 nodejs 環境的,只是這一切,我們不需要關心。我們只需要關心我們這個雲函數提供的功能是什麼就可以了。

創建雲函數很簡單,直接在開發工具中右鍵“新建Node.js 雲函數”。然後以創建訂單為例,假設我們創建一個雲函數名為c-order-add,創建好了之後,目錄是這樣:

雲函數的主要代碼在 index.js 中,其完整代碼是這樣:

// 雲函數入口文件
const cloud = require('wx-server-sdk')
cloud.init({
  env: 'release-xxx'// your-env-id
})
const db = cloud.database()

// 雲函數入口函數
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext();
  console.log("雲函數 c-order-add : ")  
  // 這裡是一些邏輯處理...
  
  return await db.collection('uorder').add({
    data: {
      openid: event.userInfo.openId,
      address: event.address,
      userName: event.userName,
      phone: event.phone,
      shoppingInfo: event.shoppingInfo,
      totlePrice: event.totlePrice,
      shoppingStr: event.shoppingStr,
      remark:event.remark,
      createTime: now,
      // ...
    }
  });
}

這個雲函數寫好之後,需要上傳到服務器,直接在雲函數目錄點擊右鍵,然後點擊“上傳並部署”即可,這就相當於部署好了後端服務。前端小程序頁面調用的寫法是這樣的:

let orderData={};
orderData.userName = this.data.userName;
orderData.phone = this.data.phone;
orderData.address = this.data.address;
// ....
wx.cloud.callFunction({
      // 雲函數名稱
      name: 'c-order-add',
      // 傳給雲函數的參數
      data: orderData,
      complete: res => {
        Dialog.alert({
          title: '提交成功',
          message: '您的訂單成功,即將配送,請保持手機通暢。'
        }).then(() => {
          // ....
          wx.redirectTo({
            url: '../uorder/uorder'
          });
        });
      }
})

這裏,向程序前端,通過wx.cloud.callFunction完成了對雲函數的調用,也可以理解為對後端服務的調用。至此我們我們介紹完了,小程序雲開發的功能。雖然,我只貼出了少量的代碼,即保存商品,和提交訂單。由於時間和篇幅有限,我不可能把整個完整的程序代碼貼出來。但是你可以參照這個用法示例,將剩下的業務邏輯補充完整,最終完成“項目構思”一節中展示的成品截圖效果。

小程序審核的一點經驗

我開發的小程序審核在提交審核的時候遭遇了兩次退回,第一次是因為:“小程序具備電商性質,個人小程序號不支持”。所以,我只好申請了一個企業小程序號,使用的是超市的營業執照。服務類目的選擇也被打回了一次,最後選擇了食品還提交了食品經營許可證。第二次打回是因為:“用戶體驗問題”。其實就是“授權索取”的問題,微信不讓打開首頁就“要求授權”,同時不能強制用戶接受授權,得提供拒絕授權也能使用部分功能。

上面兩條解決之後,更新新了好幾版,都沒有出現過被拒的情況。並且,有次我是夜晚 10 左右提價的審核,結果10 點多就提示審核通過,當時沒看具體時間,就是接盆水泡了個腳的時間審核通過了。所以,我推斷小程序審核初次審核會比較嚴,之後如果改動不大應該直接機審就過了。

總結及對比

這裏我們可以對小程序雲開發和傳統模式做一個對比:

對比條目 傳統模式 雲開發
是否需要後端服務 需要 (如一個java應用部署在 Tomcat 中) 不需要 只需要“雲函數”
是否需要域名 需要 (還得在微信後台的把域名加入安全域名) 不需要
是否需要購買服務器 需要 (你得部署後端 Java 應用,還得安裝數據庫) 不需要
開通雲開發之後免費套餐夠用
不夠的話購買套餐按調用量計費
是否需要懂運維 需要
(你得會折騰服務器,數據庫之類的
還得配置好相關的用戶,端口,啟動服務)
不需要
圖片上傳及 CDN 麻煩 簡單
獲取微信 openID 麻煩 超級簡單,雲函數中直接獲取
···

就對比這麼多吧,總之,我非常喜歡小程序雲開發,小程序真的可以讓你輕鬆干全棧。或者咱們別動不動就提“全棧”,姑且說,小程序雲開發可以讓你更簡單、更快速、更便宜的實現你的產品落地。我自己開發的雲小程序上線之後,使用了一兩個月,沒出現任何問題。我也不用操心服務器什麼的。所以,我已經給身邊很多人安利了小程序雲開發了。這裏我就不貼出我的小程序碼了,因為已經正式給我同學的超市使用了,所以不方便讓別人去產生測試數據。如果你感興趣想看的話,可以聯繫我。

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

【面經系列】一線互聯網大廠前端面試技巧深入淺出總結

一二面(基礎面)

1. 一面基礎面

1.1 面試準備

1.1.1 個人簡歷

  • 基本信息:姓名-年齡-手機-郵箱-籍貫
  • 工作經歷:時間-公司-崗位-職責-技術棧-業績(哪些成就)
  • 學歷: 博士 > 碩士 > 本科 > 大專
  • 工作經歷:時間-公司-崗位-職責-技術棧-業績
  • 開源項目:GitHub和說明

1.2.2 自我陳述

1.2.2.1 把我面試的溝通方向(別把自己帶到坑裡面)

答:我平時喜歡研究一些網站,並對一些技術的原理和好玩的點感興趣,我自己也喜歡思考,也喜歡嘗試探索有沒有更好的方式和實現。(有所收留,不要全部說出來,稍微留一點懸念留作面試官來提問)

1.2.2.2 豁達、自信的適度發揮

答:適當自信,向自己擅長的方向上面來引路;要讓面試官來欣賞我,而不是來鄙視他。

1.2.2.3 自如談興趣

(豁達自信,適當收住),巧妙演示實例,適時討論疑問(不知道的問題請求指導一下,如何去解決,不要說不知道,或者不了解)

1.2.2.4 節奏要適宜

切忌小聰明(盡量把問題的所有實現方法都寫出來,表現出來的是熟練)

1.2 面試實戰

[!NOTE]
> 1. 方向要對,過程要細(性能優化,過程詳細)
> 2. 膽子要大、心態要和(算法題認真思考,認真使勁想;敢於承擔責任,不要輕易放棄)

2. CSS相關

2.1 頁面布局

2.1.1 如何實現垂直居中布局呢?

1.已知寬高

/*v1*/
.container {
    position: absolute;
    left: 50%;
    top: 50%;
    marigin-left: -width / 2;
    marigin-top: -width / 2;
}

/*v2*/
.container {
    position: absolute;
    top: calc(50% - 5em);
    left: calc(50% - 9em);
}

2.未知寬高

/*v1*/
.container {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

/*v2:flex+ auto*/
.wrapper {
    dislay: flex;
}
.content {
    margin: auto;
}

/*v3. 父元素居中*/
.wrapper {
    display: flex;
    /* 盒子橫軸的對齊方式 */
    justify-content: center;
    /* 盒子縱軸的對齊方式 */
    align-items: center;
}

/*v4.body內部居中*/
.content {
     /* 1vh = 1% * 視口高度 */
      margin: 50vh auto;
      transform: translateY(-50%);
}

2.1.2 如何實現水平居中布局呢?

  1. 如果需要居中的元素為常規流中 inline / inline-block 元素,為父元素設置 text-align: center;
  2. 父元素上設置 text-align: center; 居中元素上margin 為 auto。
  3. 如果元素positon: absolute; 那麼
    • 0)設置父元素postion: relative
    • 1)為元素設置寬度,
    • 2)偏移量設置為 50%,
    • 3)偏移方向外邊距設置為元素寬度一半乘以-1

2.1.3 如何實現三欄布局呢?

  1. left和right寫在center前面,並且分別左右浮動;
  2. 左右區域分別postion:absolute,固定到左右兩邊;中間的這個div因為是塊級元素,所以在水平方向上按照他的包容塊自動撐開。
  3. 父元素display: table;並且寬度為100%; 每一個子元素display: table-cell; 左右兩側添加寬度,中間不加寬度
  4. 包裹這個3個塊的父元素display: flex; 中間的元素flex: 1;
  5. 網格布局
/* 網格布局 */
.wrapper {
    display: grid;
    width: 100%;
    grid-template-columns: 300px 1fr 300px;
}

2.2 知道CSS動畫的實現嗎?

[!NOTE]
知道transition 過渡動畫和animation 關鍵幀動畫區別和具體實現。

  • 1.CSS動畫實現輪播圖
  • 2.CSS動畫實現旋轉的硬幣
  • 3.CSS動畫實現鐘擺效果

2.3 CSS盒子模型

2.3.1 說一下CSS的盒子模型?標準模型和IE模型的區別?CSS如何設置這兩種模型?

  • 標準盒子模型:width = content
  • IE盒子模型:width = content + pading + border

  • box-sizing : content-box
  • box-sizing : border-box

2.4 CSS樣式獲取

2.4.1 JS如何設置獲取盒子模型對應的寬度和高度?(面試重點)

  • dom.style.width/height : 只能取到內聯樣式的的屬性信息(拿不到外部引入的CSS樣式信息的)
  • dom.currentStyle.width/height : 會拿到瀏覽器渲染之後的屬性信息(IE瀏覽器)
  • window.getComputedStyle(dom).width/height : Chrome/Firefox 兼容, Firefox可以通過document.defaultView.getComputedStyle(dom)的方式來獲取
  • dom.getBoundingClientRect().width/height : 可以獲取距離viewport位置的寬度和高度

2.5 BFC

2.5.1 根據盒子模型解釋邊距額重疊問題?邊距重疊問題的解決方案?

  • 父子元素
  • 兄弟元素
  • 其他 ————————–計算方式:以參數的最大值來進行計算

解決方案:對父級元素創建BFC

2.5.2 BFC原理

[!NOTE]
BFC: 塊級格式化上下文,IFC(內聯格式化上下文)

  1. 在BFC的垂直邊距上面會發生重疊
  2. BFC的區域不會與浮動元素的BOX重疊
  3. BFC在頁面上是一個獨立的渲染區域,外部的元素不會影響到我,同時也不會影響到外部的元素
  4. 計算BFC的高度的時候,浮動元素也會參与運算

2.5.3 如何創建BFC?

  1. float值不是none
  2. position值不是static或者relative
  3. display值為table, table-cell, inline-box1.
  4. overflow : auto/hidden

2.5.4 BFC的使用場景?(重點理解)

  1. 解決邊距的重疊問題
<section id="margin">
        <style>
            #margin {
                background-color: #4eff35;
                overflow: hidden;
            }
            #margin>p {
                /*上 左右 下*/
                margin: 5px auto 25px;
                background-color: #ff255f;
            }
        </style>
        <p>1</p>
        <!--把一個元素放在一個容器裏面,為這個容器創建BFC即可解決邊距重疊問題-->
        <div style="overflow: hidden">
            <p>2</p>
        </div>

        <p>3</p>
</section>
  1. BFC 不與float部分重疊的解決
<section id="layout">
      <style>
          #layout {
              background-color: #48adff;
          }
          #layout .left {
              float: left;
              height: 300px;
              width: 200px;
              background-color: #ff4344;
          }
          #layout .right {
              height: 400px;
              background-color: #ff255f;
              /*給右邊的這個盒子容器創建一個BFC, 這個容器裏面的內容就會沿着垂直方向延伸*/
              overflow: auto;
              /*overflow: auto;*/
              /*display: table;*/
              /*float: left;*/
              /*position: fixed;*/
          }
      </style>
      <div class="left">
          LEFT
      </div>
      <div class="right">
          RIGHT
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
          <p>111</p>
      </div>
  </section>
  1. BFC子元素即使是float元素也要參与運算
<section id="float">
      <style>
          /*一個盒子內部的內容如果是浮動的話,那麼這個盒子的內容實際上是不參与父容器高度計算的*/
          #float {
              background-color: red;
              /*overflow: hidden;*/
              float: left;
          }
          #float .float {
              float: left;
              font-size: 30px;
          }
      </style>
      <div class="float">
          我是浮動的元素
      </div>
</section>

3. 事件相關

3.1 DOM事件

3.1.1 DOM事件的級別有哪些?

[!NOTE]
DOM級別一共可以分為四個級別:DOM0級、DOM1級、DOM2級和DOM3級。而DOM事件分為3個級別:DOM0級事件處理,DOM2級事件處理和DOM3級事件處理。

  1. DOM0 : element.onclick = function(e) {}
    DOM1 :該標準中未涉及到事件綁定的相關東西
  2. DOM2 : element.addEventListener(‘click’, function(e){}, false), 一個DOM元素可以添加多個事件
  3. DOM3 : element.addEventListener(‘keyup’, function(e){}, false),在DOM2標準基礎上面增加了新的事件類型:鼠標事件,鍵盤事件,焦點事件

3.1.2 DOM事件模型有哪些?

  1. 事件捕獲:從外向內, window -> document -> body -> button
  2. 事件冒泡:從內向外,button -> body -> document -> window

3.1.3 DOM事件流?

瀏覽器為當前的頁面與用戶進行交互的過程中,點擊鼠標後事件如何傳入和響應的呢?

    1. 捕獲階段:從外部容器開始向內
    1. 目標階段:事件通過捕獲到達目標階段
    1. 冒泡階段:從目標元素再上傳到window對象

3.1.4 什麼事件可以代理?什麼事件不可以代理呢?

什麼樣的事件可以用事件委託,什麼樣的事件不可以用呢?

[!NOTE]

  1. 通常支持事件冒泡(Event Bubbling)的事件類型為鼠標事件和鍵盤事件,例如:mouseover, mouseout, click, keydown, keypress。
  2. 接口事件(指的是那些不一定與用戶操作有關的事件)則通常不支持事件冒泡(Event Bubbling),例如:load, change, submit, focus, blur。

很明顯:focus 和 blur 都屬於不支持冒泡的接口事件。既然都不支持冒泡,那又如何實現事件代理呢?

3.1.5 IE和DOM事件流的區別?

IE採用冒泡型事件 Netscape使用捕獲型事件 DOM使用先捕獲后冒泡型事件

  1. 冒泡型事件模型: button -> div -> body (IE瀏覽器本身只支持Bubbling不支持Capturing)
  2. 捕獲型事件模型: body -> div-> button (Netscape事件流,網景瀏覽器公司)
  3. DOM事件模型: body -> div -> button -> button -> div -> body (先捕獲后冒泡,除了IE以外的其他瀏覽器都支持標準的DOM事件處理模型)

[!NOTE]

  • 規範和瀏覽器實現的差別?
    • DOM2級事件規範的捕獲階段,事件從文檔節點document開始傳播,現代瀏覽器大多數都是從window對象開始傳播事件的;
    • DOM2級事件規範捕獲階段不涉及事件目標,現代瀏覽器大多數都在這個階段包含事件目標。

3.1.6 事件對象event的屬性方法的差別?

        IE                    DOM
cancelBubble = true    stopPropagation()    // 停止冒泡
returnValue = false    preventDefault()     // 阻止元素默認事件
srcEelement            target               // 事件目標

3.1.7 描述DOM事件捕獲的具體流程?

window -> document -> HTML標籤 -> body -> … -> 目標元素

[!NOTE]
關鍵點: 注意根節點是window這個對象的

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div id="container">
      <style>
          #container {
              width: 200px;
              height: 200px;
              background-color: #ff255f;
          }
      </style>
  </div>
  <script>
      // 事件捕獲機制
      window.addEventListener('click', function(){
          console.log('window capture');
      }, true)
      document.addEventListener('click', function () {
          console.log('document capture');
      }, true)
      document.documentElement.addEventListener('click', function () {
          console.log('HTML capture');
      }, true)
      document.body.addEventListener('click', function () {
          console.log('body capture');
      }, true)
      document.getElementById('container').addEventListener('click', function () {
          console.log('container capture');
      }, true)

      // 事件冒泡機制
      window.addEventListener('click', function(){
          console.log('window capture');
      })
      document.addEventListener('click', function () {
          console.log('document capture');
      })
      document.documentElement.addEventListener('click', function () {
          console.log('HTML capture');
      })
      document.body.addEventListener('click', function () {
          console.log('body capture');
      })
      document.getElementById('container').addEventListener('click', function () {
          console.log('container capture');
      })

      // 輸出結果
      window capture  --> document capture --> HTML capture --> body capture --> container capture --> container capture -->  body capture --> HTML capture --> document capture --> window capture
  </script>
</body>
</html>

3.1.8 如何拿到HTML這個標籤節點元素呢?(加分項)

  var html = document.documentElement;

3.1.9 描述Event對象的常見應用?

  1. e.preventDefault() : 阻止默認事件(如阻止a標籤的默認跳轉行為)
  2. e.stopPropagation() : 阻止事件冒泡的行為
  3. *** e.stopImmediatePropagation() : 事件響應的優先級的應用場景,如果一個元素綁定了多個事件,但是又不想讓其他的事件執行的時候使用該方法【也會阻止冒泡】
  4. e.currentTarget : 當前所綁定的事件對象
  document.documentElement.onclick = function(e) {
    console.log(e.currentTarget, e.target);       // <html><body>...</body></html>()給綁定事件的那個元素, 當前被點擊的那個元素
  }

[!NOTE]
e.target : 當前被點擊的元素,父元素使用事件代理的方式來實現,可以直接使用該屬性獲取被點擊的那個元素

3.2 如何自定義事件?(重點))

3.2.1 如何給一個按鈕綁定一個自己定義的事件呢?

  // v1. 使用Event對象來自定義事件
  // 開始創建一個自己定義的事件對象
  var eve = new Event('customEvent');
  // 使用dom2事件處理的方式來給這個元素綁定一個事件
  var dom = document.documentElement;
  dom.addEventListener('customEvent', function(e) {
    console.log('customEvent called!');
  });
  // 下面的這句話可以在適合的場景中來觸發一個自己定義的事件對象
  setTimeout(function(){
    // 在1s之後觸發這個事件
    dom.dispatchEvent(eve);
  }, 1000)


  // v2. 使用CustomEvent來實現自定義事件
  var dom = document.documentElement;
  // 使用CustomEvent的方式可以在事件觸發的時候傳遞一個參數,然後通過e.detail 的方式來獲取這個參數信息
  var myClick = new CustomEvent('myClick', {detail : {name : 'zhangsan', age : 24}});
  dom.addEventListener('myClick', function(e){
    console.log(e.detail, e.target)
  })
  dom.dispatchEvent(myClick);

4. HTTP協議

4.1 HTTP協議的主要特點?

  • 簡單快速
  • 靈活
  • 無連接
  • 無狀態

4.2 HTTP報文的組成部分?

  • 請求報文
    請求行:請求方法 資源地址 HTTP版本
    請求頭: key : value
    空行 :
    請求體 : name=zhangsan&age=18
  • 響應報文 : HTTP版本 狀態碼
    狀態行
    響應頭
    空行
    響應體

4.3 HTTP方法?

  • GET : 獲取資源
  • POST : 傳輸資源
  • PUT :更新資源
  • DELETE : 刪除資源
  • HEAD :獲取報文首部
  • OPTIONS : 允許客戶端查看服務器的性能。

4.4 POST和GET的區別?

  1. GET請求在瀏覽器回退的時候是無害的,而POST會再次提交請求
  2. GET請求產生的URL地址可以被收藏,而POST不可以
  3. GET請求會被瀏覽器主動緩存,而POST不會,除非主動設置
  4. GET請求只能進行URL編碼,而POST支持多種編碼方式
  5. GET請求參數會被完整第保留在瀏覽器的歷史記錄裏面,而POST參數不會被保留
  6. GET請求愛URL中傳送的參數的長度是有限的(2KB),而POST沒有限制
  7. 對參數的數據類型,GET值接受ASCII字符,而POST沒有限制
  8. POST比GET更安全,GET參數直接暴露在URL上,所以不能用來傳遞敏感信息
    9. GET參數通過URL傳遞,POST參數直接放在了Request body中

4.5 HTTP狀態碼?

4.5.1 狀態碼的第一位

  • 1xx :指示信息-表示請求已接收,繼續處理(重點)
  • 2xx :成功-表示請求已被成功接收
  • 3xx :重定向-要完成請求必須進行更進一步的操作
  • 4xx :客戶端錯誤-請求有語法錯誤或請求無法實現
  • 5xx :服務器錯誤-服務器未能實現合法的請求

4.5.2 狀態碼詳解

  • 200 OK : 客戶端請求成功
  • 206 Partial Content : 客戶端發送了一個帶有Range頭的GET請求(Video標籤或者audio標籤在請求數據的時候)
  • 301 Moved Permanently : 請求的頁面已經轉移到了新的URL
  • 302 Found : 所請求的頁面已經臨時轉移到了新的URL
  • 304 Not Modified :客戶端有緩衝的文檔併發出了一個條件下的請求,原來緩衝的文檔還可以繼續使用
  • 400 Bad Request : 客戶端請求有語法錯誤,不被服務器所理解
  • 401 Unauthorized : 請求未經授權,這個狀態碼必須和WWW-Authenticate報頭域一起使用
  • 403 Forbidden:對被請求頁面的訪問被禁止
  • 404 Not Found : 請求資源不存在
  • 500 Internal Server Error :服務器發生不可預期的錯誤,原來緩衝的文檔還可以繼續使用
  • 503 Service Unavailable : 請求未完成,服務器臨時過載或宕機,一段時間后可能恢復正常

4.6 什麼是持久連接?

[!NOTE]
HTTP協議採用‘請求-應答’模式, HTTP1.1版本才支持的,使用Keep-alive字段可以建立一個長連接,從而不需要每次請求都去建立一個新的連接。

4.7 什麼是管線化?

4.7.1 基本概念

  • 在使用持久連接(Keep-alive)的情況下,某個連接上的消息的傳遞類似於:請求1 –> 響應1 –> 請求2 –> 響應2 –> 請求3 –> 響應3
  • 管線化的過程: 請求1 –> 請求2 –> 請求3 –> 響應1 –> 響應2 –> 響應3

4.7.2 管線化的特點(特點)

  1. 管線化機制通過持久連接完成,僅在HTTP1.1版本之後支持
  2. 只有GET和HEAD請求可以進行管線化,POST有所限制的
  3. 初次創建連接的時候不應該啟動管線機制,因為對方(服務器)不一定支持HTTP1.1版本的協議
  4. 管線化不會影響到響應到來的順序,HTTP響應返回的順序並未改變
  5. HTTP1.1 要求服務器支持管線化,但並不要求服務器也對響應進行管線化處理,只是要求對於管線化的請求不失敗即可
  6. 由於上面提到的服務器端問題,開啟管線化很可能並不會帶來大幅度的性能提升,而且很多服務器和代理程序對管線化的支持並不好,因此現代的瀏覽器如Chrome和Firefox默認並沒有開啟管線化支持

5. 原型鏈

5.1 創建對象的幾種方法?

// 1. 使用字面量的方式來創建
var o1 = {name : 'zhangsan'};
var o11 = new Object({name : 'zhangsan'});

// 2. 使用普通構造函數的方式來創建
var M = function(){
    this.name = 'zhangsan';
}
var o2 = new M();

// 3. Object.create方法
var p = {name : 'zhangsan'};
var o3 = Object.create(p);

5.2 原型、構造函數、實例、原型鏈?

構造函數:使用new運算符來聲明一個實例(任何函數都是可以通過構造函數來使用的)

原型鏈:通過原型鏈可以找到上一級別的原型對象

原型對象:多個實例公用的數據和屬性或者方法

5.3 instanceof的原理?

[!NOTE]
instanceof 檢測一個對象A是不是另一個對象B的實例的原理是:查看對象B的prototype指向的對象是否在對象A的[[prototype]]鏈上。如果在,則返回true,如果不在則返回false。不過有一個特殊的情況,當對象B的prototype為null將會報錯(類似於空指針異常)。

// 2. 使用普通構造函數的方式來創建
var M = function(){
  this.name = 'zhangsan';
}
var o2 = new M();
undefined
o2.__proto__ == M.prototype
true
o2.__proto__ == M.prototype
true
o2.__proto__.constructor === Object
false
o2.__proto__.constructor === M
true

5.4 new運算符的原理?

  1. 一個新對象被創建。它繼承於foo.prototype
  2. 構造函數foo被執行。執行的時候,相應的傳參會被傳入,同時上下文(this)會被指定為這個新實例,new foo等同於 new foo(),只能用在不傳遞任何參數的情況
  3. 如果構造函數返回了一個“對象”,那麼這個對象會取代整個new出來的結果。如果構造函數沒有返回對象,那麼new 出來的結果為步驟1創建的對象
// new 一個對象的過程
var _new = function (fn) {
  // 1. 創建一個對象,這個對象要繼承fn這個構造函數的原型對象
  var o = Object.create(fn.prototype);
  // 2. 執行構造函數
  var k = fn.call(o, arguments);
  // 3. 看下執行的這個函數的運行效果是不是函數
  if (typeof k === 'object'){
      return k;
  }
  else
  {
      return o;
  }
}

6. 面向對象

6.1 類與繼承:如何實現繼承,繼承的幾種實現方式

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<script>
  // 類的聲明
  function Animal1() {
      this.name = 'name';
  }
  // ES6 中的class的聲明
  class Animal2 {
      constructor(){
          this.name = 'name';
      }
  }

  console.log(new Animal1(), new Animal2());
  ///////////////////////////////////////////////////////////////////////////////////////////


  // 如何實現類的繼承呢???-----------本質:原型鏈
  // v1. 藉助構造函數實現繼承
  function Parent1() {
      this.name = 'parent1'
  }
  Parent1.prototype.sayHello = function () {
      console.log('hello');
  }
  function Child1() {
      // 執行父親的構造函數:
      // 1. 實現原理:將父級函數的this指向了這個子類的實例上面去了
      // 2. 缺點:父親的原型鏈上面的方法或者屬性不能被繼承;只能實現部分繼承
      Parent1.call(this);
      this.type = 'child1';
  }
  // 沒有參數的時候,可以直接new + 函數名稱
  console.log(res = new Child1);




  // v2. 藉助原型鏈實現繼承
  function Parent2() {
      this.name = 'parent2';
      this.data = [1, 2, 3];
  }
  Parent2.prototype.sayHello = function () {
      console.log('hello');
  }
  function Child2() {
      this.type = 'child2';
  }
  // prototype 就是為了讓這個對象的實例可以訪問到原型鏈上的內容
  Child2.prototype = new Parent2();
  // new Child2().__proto__ === Child2.prototype  // true
  // new Child2().__proto__.name                  // parent2
  // 原型鏈繼承的缺點:
  // 1. 原理:通過修改原型鏈來實現對象的繼承關係
  // 2. 缺點:修改第一個對象上面的屬性,會直接修改第二個對象屬性數據(引用類型)
  var c1 = new Child2();
  var c2 = new Child2();
  c1.data.push(100, 200, 300);

  // v3. 組合繼承
  function Parent3() {
      this.name = 'parent3';
      this.data = [1, 2, 3];
  }
  function Child3() {
      // 1. 借用構造函數繼承
      Parent3.call(this);
      this.type = 'child3';
  }
  // 2. 原型鏈繼承
  // child3的原型對象是Parent3的一個實例對象,但是這個實例對象中是沒有constructor這個屬性的,因此尋找屬性的時候回沿着這個實例對象的原型鏈繼續向上尋找new Parent3().prototype 這個原型對象的,
  // 最終在Parent3.prototype這個原型對象中找到了這個屬性,new一個對象找的實際上是{Parent3.prototype.constructor : Parent3}
  Child3.prototype = new Parent3();
  var c1 = new Child3();
  var c2 = new Child3();
  c1.data.push(100, 200, 300);
  // 組合繼承的特點:
  // 1. 原理:結合借用構造函數繼承和原型鏈繼承的優點,摒棄二者的缺點
  // 2. 缺點:父類構造函數在創建實例的時候總共執行了兩次(new Parent3(), new Child3())


  // v4. 組合繼承的優化1
  function Parent4() {
      this.name = 'parent4';
      this.data = [1, 2, 3];
  }
  function Child4() {
      // 1. 借用構造函數繼承
      Parent4.call(this);
      this.type = 'child4';
  }
  // 讓子類的構造函數的原型對象和父類構造函數的原型對象執向同一個對象(都是同一個對象)
  Child4.prototype = Parent4.prototype;
  // 測試
  var c1 = new Child4();
  var c2 = new Child4();
  console.log(c1 instanceof Child4, c1 instanceof Parent4);
  console.log(c1.constructor)         // Parent4? 如何實現:c1.constructor(c1.__proto__.constructor) === Child4 呢?
  // 缺點:
  // 1. 無法通過原型對象的constructor屬性來獲取對象的屬性對應的構造函數了(子類和父類公用的是一個contructor)
  // 2. obj instanceof Child4 === true; obj instanceof Parent4 === true
  // 3. obj.__proto__.constructor === Child4; obj.__proto__.constructor === Parent4  ???

  // v5. 組合繼承的優化2【完美寫法】
  function Parent5() {
      this.name = 'parent5';
      this.data = [1, 2, 3, 4, 5];
  }
  function Child5(){
      Parent5.call(this);
      this.type = 'child5';
  }

  // 通過創建中間對象的方式來把兩個對象區分開
  // var obj = new Object(); obj.__proto__ = Constructor.prototype;
  // 1. Object.create創建的對象obj, 這個obj的原型對象就是參數
  // 2. Child5的原型對象是Child5.prototype
  // 3. Child5.prototype = obj,obj這個對象相當於就是一个中間的橋樑關係
  Child5.prototype = Object.create(Parent5.prototype);
  // 當前的方式還是會按照原型鏈一級一級向上尋找的, 給Child5的原型對象上面綁定一個自己定義的constructor屬性
  Child5.prototype.constructor = Child5;

  // var s1 = new Child5()

  // 上面的代碼等價於
  var obj = Object.create(Parent5.prototype);     // obj.prototype = Parent5.prototype
  Child5.prototype = obj;
  Child5.prototype.constructor = Child5;
  // 1. 對象之間就是通過__proto__ 屬性向上尋找的
  // 2. 尋找規則: child5 ---> Child5.prototype ---> obj(Object.create(Parent5.prototype)) ---> Parent5.prototype


  // 技巧:不要讓面試官問太多題目:拖拉時間【擠牙膏】,把一個問題盡量吃透
  // 消化這一塊內容
</script>
</body>
</html>

[!WARNING]
面試技巧

  1. 不要讓面試官問太多題目:拖拉時間【擠牙膏】,把一個問題盡量吃透
  2. 知識深度

7. 通信

7.1 什麼是同源策略個限制?

[!NOTE]
同源策略限制是從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用於隔離潛在惡意文件的關鍵的安全機制。(一個源的文檔或腳本是沒有權利直接操作另外一個源的文檔或腳本的)

  1. Cookie, LocalStorage和IndexDB無法讀取
  2. DOM無法獲得;(document.body是無法獲取的)
  3. Ajax請求不能發送

7.2 前後端如何進行通信呢?

  1. Ajax(有同源策略限制);Fetch API則是XMLHttpRequest的最新替代技術, 它是W3C的正式標準
  2. WebSocket:支持跨域請求數據,沒有同源策略的限制
  3. CORS:新的協議通信標準;CORS則將導致跨域訪問的請求分為三種:Simple Request,Preflighted Request以及Requests with Credential;cors相對於jsonp而言的好處就是支持所有的請求方式,不止是get請求,還支持post,put請求等等,而它的缺點就很明顯,無法兼容所有的瀏覽器,對於要兼容到老式瀏覽器而言,還是使用jsonp好點

7.3 如何創建Ajax呢?

  1. XMLHttpRequest對象的工作流程
  2. 瀏覽器的兼容性處理【重點】
  3. 事件的觸發條件
  4. 事件的觸發順序
  function ajax(params){
    // 1. 創建對象,考慮兼容性【重點】
    var xhr = XMLHTTPRequest ? new XMLHTTPRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');      // *** 兼容性問題必須考慮
    // 2. 打開連接
    var type = params.type || 'GET',
        url = params.url || '',
        data = params.data || {},
        success = params.success,
        error = params.error,
        dataArr = [];
    for (var k in data) {
      dataArr.push(k + '=' + data[k]);
    }
    //帶上Cookie
    xhr.withCredentials = true;
    if (type.toUpperCase() === 'GET') {
      // get
      url += '?' + dataArr.join('&');
      // 問號結尾的話,直接替換為空字符串
      xhr.open(type, url.replace(/\?$/g, ''), true);
      // GET 請求的話,是不需要再send方法中帶上參數的
      xhr.send();
    }
    else {
      // POST
      xhr.open(type, url, true);
      xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
      // POST 請求需要把數據放在send方法裏面, data = name=zhangsna&age=18&sex=male
      xhr.send(dataArr.join('&'));
    }
    // 開始監聽變化
    xhr.onreadystatechange = function(){
      // 這裏需要考慮強緩存和協商緩存的話直接處理,206是媒體資源的創建方式
      if (xhr.readyState === 4 && xhr.status === 200 || xhr.status === 304) {
          var res;
          if (success instanceof Function) {
            res = xhr.responseText;
            if (typeof res === 'string') {
              res = JSON.parse(res);
              // 開始執行成功的回調函數
              success.call(xhr, res);
            }
          } else {
            if (error instanceof Function) {
              // 失敗的話直接返回這個responseText中的內容信息
              error.call(xhr, res);
            }
          }
      }
    }
  }

7.4 跨域通信的幾種方式?

7.4.1 JSONP

  function jsonp(url, onsuccess, onerror, charset){
    // 1. 全局註冊一個callback
    var callbackName = 'callback' + Math.random() * 100;
    window[callbackName] = function(){
      if (onsuccess && typeof onsuccess === 'Function') {
        onsuccess(arguments[0]);
      }
    }
    // 2. 動態創建一個script標籤
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    charset && script.setAttribute('charset', charset);
    script.setAttribute('src', url);
    script.async = true;
    // 3. 開始監聽處理的過程
    script.onload = script.onreadystatechange = function(){
      if (!script.readyState || /loaded|complete/.test(script.readyState)) {
        // 4. 成功之後移除這個事件
        script.onload = script.onreadystatechange = null;
        // 刪除這個script的DOM對象(head.removeChild(script), 這個DOM節點的父節點相當於是head標籤這個父節點)
        script.parentNode && script.parentNode.removeChild(script);
        // 刪除函數或變量
        window[callbackName] = null;
      }
    }
    script.onerror = function(){
      if (onerror && typeof onerror === 'Function') {
        onerror();
      }
    }
    // 5. 開始發送這個請求(把這個標籤放在頁面中的head標籤中即可)
    document.getElementsByTagName('head')[0].appendChild(script);
  }

7.4.2 Hash

hash 改變后頁面不會刷新的

[!NOTE]
使用場景:當前的頁面A通過iframe或者frame嵌入了跨域的頁面

  // 1. A頁面中的代碼如下
  var B = document.getElementsByTagName('iframe');
  B.src = B.src + '#' + JSON.stringfy(data);
  // 2. B中的偽代碼如下
  window.onhashchange = function(){
    var data = window.location.hash;    // 接受數據
    data = JSON.parse(data);
  }

7.4.3 postMessage(HTML5中新增)

[!NOTE]
使用場景: 可以實現窗口A(A.com)向窗口B(B.com)發送信息

  // 1. 窗口B中的代碼如下
  var BWindow = window;
  BWindow.postMessage(JSON.stringfy(data), 'http://www.A.com');   
  // 2. 窗口A中代碼
  var AWindow = window;
  AWindow.addEventListener('message', function(e){
      console.log(e.origin);                  // http://www.B.com
      console.log(e.source);                  // BWindow

      e.source.postMessage('已成功收到消息');

      console.log(JSON.parse(e.data));        // data
  }, false)
  // 父窗口給子窗口發信息,需要用iframe的contentWindow屬性作為調用主體
  // 子窗口給父窗口發的信息需要使用window.top,多層iframe使用window.frameElement

7.4.4 . WebSocket

[!NOTE]
不受同源策略影響,可以直接使用

  var ws = new window.WebSocket('ws://echo.websocket.org');

  // 打開連接
  ws.onopen = function(e){
    console.log('Connection open ……');
    ws.send('Hello WebSocket!');
  }

  // 接受消息
  ws.onmessage = function(e){
    console.log('Received Message : ', e.data);
  }

  // 關閉連接
  ws.onclose = function(e){
    console.log('Connection closed');
  }

7.4.5 CORS

支持跨域通信版本的Ajax,是一種新的標準(Origin頭)【ajax的一個變種,適用於任何】

  fetch('/get/name', {
    method : 'get'
  }).then(function(response){
    console.log(response);
  }).catch(function(err){
    // 出錯了;等價於then的第二個參數
  });
  // 原因:瀏覽器默認會攔截ajax請求,會根據頭中的origin消息進行判斷處理消息;Origin字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。JSONP只支持GET請求,CORS支持所有類型的HTTP請求。JSONP的優勢在於支持老式瀏覽器,以及可以向不支持CORS的網站請求數據。
7.4.5.1 CORS請求的基本流程
  1. 對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin字段。
  2. Origin字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。
  3. 如果Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP回應。瀏覽器發現,這個回應的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。
  4. 如果Origin指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段。
  Access-Control-Allow-Origin: http://api.bob.com   // 必需的字段
  Access-Control-Allow-Credentials: true            // 可選字段: 是否允許發送cookie
  Access-Control-Expose-Headers: FooBar
  Content-Type: text/html; charset=utf-8
  1. 簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為”預檢”請求(preflight)。OPTIONS表示當前的這個請求是用來詢問的;服務器收到”預檢”請求以後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以後,確認允許跨源請求,就可以做出回應。
7.4.5.2 JSONP和CORS的區別?
  1. JSONP只支持GET請求,CORS支持所有類型的HTTP請求
  2. JSONP的優勢在於支持老式瀏覽器,以及可以向不支持CORS的網站請求數據。

8. 安全

8.1 CSRF

8.1.1 基本概念和縮寫

CSRF: 跨站請求偽造,Cross site request forgery

8.1.2 CSRF 攻擊原理

8.1.3 可以成功攻擊的條件?

  1. 目標網站存在CSRF漏洞的請求接口(一般為get請求)
  2. 目標用戶之前已經成功登錄過這個網站(留下了Cookie)

8.1.4 如何防禦呢?

  1. Token驗證:訪問服務器接口的時候,會自動帶上這個token
  2. Referer驗證:驗證網站的頁面來源(只有我當前網站下的頁面才可以請求,對於來自其他網站的請求一律攔截)
  3. 隱藏令牌: 隱藏信息會放在header中(類似於Token)

8.2 XSS

8.2.1 基本概念和縮寫

XSS: cross-site scripting, 跨站腳本攻擊

8.2.2 XSS防禦

攻擊原理: 注入JS腳本

防禦措施: 讓JS代碼無法解析執行

8.3 CSRF和XSS的區別呢?

  1. CSRF:網站本身存在漏洞的接口,依賴這些登錄過目標網站的用戶來實現信息的竊取;
  2. XSS:向頁面中注入JS執行,JS函數體內執行目標任務;

[!NOTE]

  1. 一定要說出中文名稱,實現原理,防範措施都說出來
  2. 不要拖泥帶水,言簡意賅

9. 算法

[!NOTE]
算法攻略:多刷題才是硬道理!!!

二三面(知識深度面)

10. 渲染機制

10.1 什麼是DOCTYPE及作用?

  1. DTD(Document Type Definition):文檔類型定義,是一系列的語法規則,用來定義XML或者(X)HTML的文件類型。瀏覽器會使用它來判斷文檔的類型,決定使用哪一種協議來解析,以及切換瀏覽器模式;
  2. DOCTYPE: 是用來聲明文檔類型和DTD規範的,一個主要的用途是文件的合法性驗證;如果文件代碼不合法,那麼瀏覽器解析的時候就會出現一些出錯
  3. 總結:Doctype就是通知瀏覽器當前的文檔是屬於那種類型的,包含哪些DTD。
  <!--HTML5的寫法-->
  <DOCTYPE html>
  <!-- HTML 4.01  Strict
    1. 這個DTD 包含所有的HTML元素和屬性
    2. 但是不包含展示性的和棄用的元素(比如font)
  -->
  <DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" >
  <!-- HTML 4.0.1 Transitional
    1. 這個DTD 包含所有的HTML元素和屬性
    2. 也包含展示性的和棄用性的元素(比如font)
  -->
  <DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" " http://www.w3.org/TR/html4/loose.dtd" >

[!NOTE]
在W3C標準出來之前,不同的瀏覽器對頁面渲染有不同的標準,產生了一定的差異。這種渲染方式叫做混雜模式。在W3C標準出來之後,瀏覽器對頁面的渲染有了統一的標準,這種渲染方式叫做標準模式。<!DOCTYPE>不存在或者形式不正確會導致HTML或XHTML文檔以混雜模式呈現,就是把如何渲染html頁面的權利交給了瀏覽器,有多少種瀏覽器就有多少種展示方式。因此要提高瀏覽器兼容性就必須重視<!DOCTYPE>

10.2 嚴格模式和混雜模式

[!NOTE]
嚴格模式和混雜模式都是瀏覽器的呈現模式,瀏覽器究竟使用混雜模式還是嚴格模式呈現頁面與網頁中的DTD(文件類型定義)有關,DTD裡面包含了文檔的規則。比如:loose.dtd

  • 嚴格模式:又稱標準模式,是指瀏覽器按照W3C標準來解析代碼,呈現頁面
  • 混雜模式:又稱為怪異模式或者兼容模式,是指瀏覽器按照自己的方式來解析代碼,使用一種比較寬鬆的向後兼容的方式來显示頁面。

10.3 瀏覽器的渲染過程?

10.3.1 開始進行DOM解析,渲染DOM Tree

10.3.2 開始進行CSS解析,渲染CSSOM Tree

10.3.3 DOM樹和CSS樹的結合,最後會轉換為Render Tree

10.3.4 Layout的過程,計算每一個DOM元素的位置、寬度、高度等信息,最終渲染並显示頁面到瀏覽器

10.4 何時會觸發Reflow?

[!NOTE]
定義:DOM結構中每個元素都有自己的盒子模型,這些都是需要根據各種樣式來計算並根據計算結果將元素放在它該出現的位置,這個過程就是reflow;

  1. 當你增加、刪除、修改DOM節點的時候,會導致Reflow或Repaint
  2. 當你移動DOM的位置,或者設置動畫的時候
  3. 當你修改CSS樣式的時候
  4. 當你Resize窗口的時候(移動端沒有這個問題,與瀏覽器有關),或者在滾動窗口的時候
  5. 當你修改網頁的默認的字體的時候

10.5 何時回觸發Repaint?

[!NOTE]
定義:當各種盒子的位置、大小以及其他屬性,例如顏色、字體大小都確定下來以後,瀏覽器於是便按照元素各自的特性繪製了一遍,於是頁面的內容出現了,這個過程就是repaint

  1. DOM改動
  2. CSS改動

10.6 如何最大程度上的減少瀏覽器的重繪Repaint過程(頻率)呢?

10.6.1 避免在document上直接進行頻繁的DOM操作,如果確實需要可以採用off-document的方式進行

    1. 先將元素從document中刪除,完成修改之後然後再把元素放回原來的位置
    1. 將元素的display設置為none, 然後完成修改之後再把元素的display屬性修改為原來的值
    1. 如果需要創建多個DOM節點,可以使用DocumentFragment創建完畢之後一次性地加入document中去
  var frag = document.createDocumentFragment();
  frag.appendChild(dom);    /*每次創建的節點先放入DocumentFragment中*/

10.6.2 集中修改樣式

  1. 盡可能少的修改元素style上的屬性
  2. 盡量通過修改className來修改樣式(一次性修改)
  3. 通過cssText屬性來設置樣式值
  document.getElementById("d1").style.cssText = "color:red; font-size:13px;";

10.6.3 緩存Layout的屬性值

[!NOTE]
對於Layout屬性中非引用類型的值(数字型),如果需要多次訪問則可以在一次訪問時先存儲到局部變量中,之後都使用局部變量,這樣可以避免每次讀取屬性時造成瀏覽器的渲染。

  var width = el.offsetWidth;
  var scrollLeft = el.scrollLeft;

10.6.4 設置元素的position為absolute或fixed

[!NOTE]
在元素的position為static和relative時,元素處於DOM樹結構當中,當對元素的某個操作需要重新渲染時,瀏覽器會渲染整個頁面。將元素的position設置為absolute和fixed可以使元素從DOM樹結構中脫離出來獨立的存在,而瀏覽器在需要渲染時只需要渲染該元素以及位於該元素下方的元素,從而在某種程度上縮短瀏覽器渲染時間。

11. 布局Layout?

Layout屬性包括

  1. offsetLeft、offsetTop、offsetHeight、offsetWidth: 相對於父對象的邊距信息,且返回值為数字;left獲取或設置相對於具有定位屬性(position定義為relative)的父對象的邊距信息,返回值為字符串10px
  2. scrollTop/Left/Width/Height:滾動條在各個方向上拉動的距離,返回值為数字
  3. clientTop/Left/Width/Height:瀏覽器的可視區域的大小
  4. getComputedStyle()、currentStyle(in IE):瀏覽器渲染DOM元素之後的寬度和高度等樣式信息

12. JS運行機制

12.1 如何理解JS的單線程?

看代碼,寫結果?

  // 同步任務
  console.log(1);
  // 異步任務要掛起
  setTimeout(function(){
    console.log(2)
  }, 0);
  console.log(3)
  // out : 1 3 2
  console.log('A');
  setTimeout(function(){
    console.log('B')
  }, 0);
  while (true) {

  }

  // out : A

12.2 什麼是任務隊列?

  for (var i = 0; i < 4; i++) {
    // setTimeout , setInterval 只有在時間到了的時候,才會把這個事件放在異步隊列中去
    setTimeout(function(){
      console.log(i);
    }, 1000);
  }
  // out : 4 4 4 4

12.3 什麼是Event Loop?

[!NOTE]
JS是單線程的,瀏覽器引擎會先來執行同步任務,遇到異步任務之後,會把當前的這個異步任務放在time模塊中,等到主線程中的所有的同步任務全部執行完畢之後;然後當前的這個異步任務只有時間到了之後,才會把這個任務(回調函數)放在一個異步隊列中;噹噹前的任務棧中的任務全部執行完畢了之後,會先去執行微任務隊列中的任務(Promise),然後等到微任務隊列中的所有任務全部執行完畢之後,再去執行process.nextTick()這個函數,等到這個函數執行完畢之後,本次的事件輪訓結束;
開啟新的執行棧,從宏任務隊列中依次取出異步任務,開始執行;每個宏任務執行都會重新開啟一個新的任務執行棧

12.3.1 3個關鍵點

  1. 執行棧執行的是同步任務;
  2. 什麼時候去異步隊列中取這個任務;
  3. 什麼時候向這個任務隊列中放入新的異步任務

    12.3.2 異步任務的分類

  • setTimeout, setInterval;
  • DOM事件(點擊按鈕的時候也會先去執行同步任務);
  • Promise

13. 知識點總結

  1. 理解JS的單線程的概念
  2. 理解任務隊列
  3. 理解Event Loop
  4. 理解哪些語句會翻入到異步任務隊列
  5. 理解與放入到異步任務隊列的時機

    13.1 頁面性能

    13.1.1 提升頁面性能的方法有哪些?

  6. 資源壓縮合併,減少HTTP請求;
  7. 非核心代碼的異步加載 —> 異步加載的方式有哪些? —> 異步加載的區別?
  8. 利用瀏覽器的緩存 —> 緩存的分類 —> 緩存的原理
  9. 使用CDN加速
  10. 預解析DNS:DNS Prefetch 是一種DNS 預解析技術,當你瀏覽網頁時,瀏覽器會在加載網頁時對網頁中的域名進行解析緩存,這樣在你單擊當前網頁中的連接時就無需進行DNS的解析,減少用戶等待時間,提高用戶體驗。(提前解析域名,而不是點擊鏈接的時候才去進行DNS域名解析,可以節省DNS解析需要耗費的20-120毫秒時間)

  <!-- https協議的網站,默認是關閉了DNS的預解析的,可以使用下面的語句開啟  -->
  <meta http-equiv="x-dns-prefetch-control" content="on">
  <!-- 開始配置需要進行DNS預解析的域名 -->
  <link rel="dns-prefetch" href="//www.zhix.net">                               <!--支持http和HTTPS-->
  <link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />               <!--支持http的協議-->
  <link rel="dns-prefetch" href="http://nsclick.baidu.com" />
  <link rel="dns-prefetch" href="http://hm.baidu.com" />
  <link rel="dns-prefetch" href="http://eiv.baidu.com" />

14. 異步加載的方式

14.1 動態腳本的加載

  var script = document.createElement('script');
  document.getElementsByTagName('head')[0].appendChild(script);

  // 沒有 defer 或 async,瀏覽器會立即加載並執行指定的腳本,“立即”指的是在渲染該 script 標籤之下的文檔元素之前,也就是說不等待後續載入的文檔元素,讀到就加載並執行。
  <script src="script.js"></script>

14.2 defer

<!-- 有 defer,加載後續文檔元素的過程將和 script.js 的加載并行進行(異步),但是 script.js 的執行要在所有元素解析完成之後,DOMContentLoaded 事件觸發之前完成。 -->
<script defer src="myscript.js"></script>

14.3 async

  <!-- 有 async,加載和渲染後續文檔元素的過程將和 script.js 的加載與執行并行進行(異步)。 -->
  <script async src="script.js"></script>

14.4 異步加載的區別?

[!NOTE]

  1. defer是在HTML解析完成之後(DOMContentLoaded事件執行之後)才會執行,如果是多個,會按照加載的順序依次執行(按照順序執行)
  2. async是在加載完之後立即執行,如果是多個,執行順序和加載順序無關(與順序無關)

15. 說一下瀏覽器的緩存機制吧?

15.1 緩存的分類

[!NOTE]
緩存目的就是為了提升頁面的性能

15.1.1 強緩存

直接從本地讀取,不發送請求

    Response Headers
    cache-control: max-age=315360000(相對時間,優先級比expires高)
    expires: Sat, 10 Mar 2029 04:01:39 GMT(絕對時間)

15.1.2 協商緩存

問一下服務器,這個文件有沒有過期,然後再使用這個文件

    Response Headers
    last-modified: Tue, 12 Mar 2019 06:22:34 GMT(絕對時間)
    etag: "52-583dfb6f4de80"

向服務器請求資源的時候,帶上if-Modified-Since或者if-None-Match這個請求頭,去詢問服務器:

    Request Headers
    if-Modified-Since: Tue, 12 Mar 2019 06:22:34 GMT
    if-None-Match: "52-583dfb6f4de80"

16. 錯誤監控/如何保證前端產品的上線質量?

16.1 前端錯誤的分類?

  1. 即時運行錯誤:代碼錯誤
  2. 資源加載錯誤:圖片/css/js文件加載失敗

16.2 錯誤的捕獲方式?

16.2.1 即時運行錯誤的捕獲方式

  // 方法一:使用try catch捕獲
  try {
    // ...
  } catch (e) {
    // error
  } finally {
    // handle error
  }

  // 方法二:使用window.onerror 捕獲錯誤
  // 無法捕獲到資源加載錯誤
  window.onerror = function(msg, url, line, col, error){
    // ...
  }  
  window.addEventListener('error', function(msg, url, line, col, error){
    // ...
  })

16.2.2 資源加載錯誤(不會向上冒泡)

  // 方法一: 直接在script, img這些DOM標籤上面直接加上onerror事件
  Object.onerror = function(e){
      // ...
  }

  // 方法二:window.performace.getEntries(間接獲取資源加載錯誤的數量)
  var loadedResources = window.performance.getEntries();           // 1. 獲取瀏覽器中已經加載的所有資源(包括各個階段的詳細加載時間)
  var loaderImgs = loadedResources.filter(item => {
      return /\.jpg|png|gif|svg/.test(item.name)
  });
  var imgs = document.getElementsByTagName('img');                // 2. 獲取頁面中所有的img集合
  var len = imgs.length - loaderImgs.length;                      // 3. 加載失敗的圖片數量
  console.log('圖片加載失敗數量:', len, '條');


  // 方法三: 使用事件捕獲的方式來實現Error事件捕獲
  // 使用事件捕獲的方式來實現資源加載錯誤的事件的捕獲:window ---> document --> html --- > body ---> div ---...
  window.addEventListener('error', function (msg) {
      console.log(msg);
  }, true);

16.2.3 補充的方法

      // 使用事件捕獲的方式來實現
     window.addEventListener('error', function (msg) {
         console.log('資源加載異常成功捕獲:', msg);
     }, true);
     // 使用事件冒泡的方式是只能捕獲到運行的時候的一些異常
     window.addEventListener('error', function (e) {
         console.log('運行異常成功捕獲1:', e.message, e.filename, e.lineno, e.colno, e.error);
     }, false);

     // 這種方式是可以按照參數的方式來接受相關的參數信息
     window.onerror = function (msg, url, line, col, error) {
         console.log('運行異常成功捕獲2:', msg, url, line, col, error);
     }

16.2.4 問題的延伸:跨域的js運行錯誤可以捕獲嗎,錯誤提示是什麼?應該怎麼處理呢?

16.2.4.1 錯誤信息

errorinfo :
Script0 error
0 row
0 col
16.2.4.2 處理方法
  1. 第一步:在script標籤上增加crossorigin屬性
  <!-- script 表情添加crossorigin屬性 -->
  <!-- 除了 script,所有能引入跨域資源的標籤包括 link 和 img 之類,都有一樣的屬性 -->
  <script crossorigin  src="http://www.lmj.com/demo/crossoriginAttribute/error.js"></script>
  1. 第二步:設置js資源響應頭’Access-Control-Allow-Origin: * ‘,服務器端需要開啟
  // 服務器可以直接設置一個響應頭信息
  res.setResponseHeader('Access-Control-Allow-Origin', 'www.lmj.com');

16.3 上報錯誤的基本原理?

  1. 採用Ajax通信的方式來上報
  2. 利用Image對象進行上報(cnzz)[重點理解掌握]
  // 下面的兩種方式都是可以實現錯誤信息的上報功能的
  (new Image).src = 'http://www.baidu.com?name=zhangsna&age=18&sex=male'
  (new Image()).src = 'https://www.baidu.com?name=zhangsan'

17. 如何使用JS獲取客戶端的硬件信息呢?

  // IE 瀏覽器提供的獲取電腦硬件的API
  var locator = new ActiveXObject ("WbemScripting.SWbemLocator");
  var service = locator.ConnectServer(".");
  var properties = service.ExecQuery("SELECT * FROM Win32_Processor");

18. 使用window.performace 來實現用戶體驗的數據記錄呢?

[!NOTE]
可以參考性能優化章節-performance性能監控一文內容。

三四面(業務項目面)

[!NOTE]

  • 知識面要廣
  • 理解要深刻
  • 內心要誠實:沒了解過,問面試官有哪些資料可以學習
  • 態度要謙虛
  • 回答要靈活:把握一個度,不要和面試官爭執對錯
    • 要學會讚美:被問住了可以回答,適當讚美(沒面試官理解的那麼深,虛心請教)

19.介紹一下你做過的項目?

19.1 項目介紹模板(業務能力體現)

  1. 我做過什麼業務?
  2. 負責的業務有什麼業績?
  3. 使用了什麼技術方案?
  4. 突破了什麼技術難點?
  5. 遇到了什麼問題?
  6. 最大的收穫是什麼?

19.2 團隊協作能力

19.3 事務推動能力

19.4 帶人能力

終面(HR面)

20. 技術終面或HR面試要點

[!NOTE]
主要考察點:樂觀积極、主動溝通、邏輯順暢、上進有責任心、有主張,做事果斷、職業競爭力、職業規劃

20.1 職業競爭力

  1. 業務能力:可以做到行業第一

  2. 思考能力:對同一件事可以從不同角度去思考,找到最優解

  3. 學習能力:不斷學習新的業務,沉澱、總結

  4. 無上限的付出:對於無法解決的問題可以熬夜、加班

20.2 職業規劃

  1. 目標是什麼:在業務上成為專家,在技術上成為行業大牛

  2. 近階段的目標:不斷的學習積累各方面地經驗,以學習為主

  3. 長期目標:做幾件有價值的事情,如開源作品、技術框架等

  4. 方式方法:先完成業務上的主要問題,做到極致,然後逐步向目標靠攏

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

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

JAVA繼承中子父類的構造方法

 

首先,構造方法本身會有一個隱式的無參構造(默認):

  ①不寫構造方法,類中的第一行代碼事實上有一個默認的無參構造(系統會隱式為你寫好)

    

public class Student {
        private String name;
//        public Student() {}      隱式地“寫”在這裏,你看不見

//  Strudent類中雖然沒有寫構造方法,但相當於有上面的無參構造
//   只不過是隱式的,你看不見

}

 

  ②只寫帶參構造方法,相當於只有該帶參構造方法(隱式的無參構造會被屏蔽無視掉,視為無效)

public class Student {
        private String name;
        public Student(String name) {
            this.name=name;
        }
//  此時原來Strudent類中的隱式的無參構造方法被屏蔽了,無效了
//  類中只有帶參構造

}

 

  ③若想同時擁有無參和帶參構造,必須顯式地寫出無參和帶參構造方法

public class Student {
        private String name;
        public Student() {}
// 顯式地將無參構造寫出來        
        public Student(String name) {
            this.name=name;
        }
//  若想Strudent類中擁有無參構造方法,必須顯式地寫出來


}

 

進一步結合繼承,就需要考慮到子父類:

  ④在子類的構造方法(無論是無參和有參)中,方法中的第一行代碼事實上都隱式地包含了父類的無參構造方法
    即: super()

public class Stu extends Student {
    private String name;
    public Stu() {
    // super();
    // 在子類的無參構造中,super()是隱式的“寫”在這裏的
    }
    
    public Stu(String name) {
    // super();
    this.name=name;
    // 在子類的帶參構造,上面的super()同樣也是隱式的“寫”在這裏的
    }

}

這就是為什麼,調用子類的構造方法時,都會先調用父類的無參構造方法了,因為默認的super()存在。

 ⑤同理,類似與上面的②,此時若寫一個有參構造,super(xx)會把隱式的super()屏蔽掉 

public class Stu extends Student {
    private String name;
    
    public Stu(String name) {
    // super();  原來隱式寫在這裏的super()被屏蔽了,無效了
    super(name);
    
    // 在子類的帶參構造, 由於的super(name)的存在,super()無效了 //此時子類的帶參構造中,只有super(name)
    }

}

 

這就是為什麼當父類沒有無參構造(即只有帶參構造——對應情況②)時,子類的構造方法編譯無法通過。這是因為子類的構造函數(帶參或無參)將調用父類的無參構造函數。 由於編譯器試圖向子類中的2個構造函數中插入super() ,但父類的默認構造函數未定義,因此編譯器會報告錯誤消息

要解決這個問題,只需要

      1)添加一個無參構造函數給父類——顯式地在父類中添加無參構造

      2)刪除父類中自定義的有參構造函數——等價於恢復了默認的無參構造
      3)將 Super(XXX) 添加到子類構造函數——通過⑤的原來來屏蔽默認的super()  

 

一些關於子父類有參無參的組合情況(其實就是排列組合)練習,有興趣的可以自己驗證一下,如下(圖片來源):

 

 

 

 

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

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

Spring框架學習總結(上)

目錄

@

1、Spring的概述

在學習SSM框架中,我建議初學者最好先學Spring框架,其次mybatis接着springMVC,先學mybatis當然也是可以的,今天我們就以絕對優雅的姿態闖進Spring世界,系好安全帶,準備好了嗎,出發了哦!!!咳咳….平時開發接觸最多的估計就是IOC容器,它可以裝載bean(所謂的bean也就是我們java中的類,當然也包括servicedao裏面),有了這個機制,我們就不用在每次使用這個類的時候為它初始化,很少看到鍵字new。另外spring的aop,事務管理等等都是我們經常用到的,可見Spring的尤為重要的作用Spring的核心是控制反轉(IoC)和面向切面(AOP)

1.1什麼是Spring

肯定有熊dei會問SE/EE開發的一站式框架所謂的一站式是什麼意思,(哼,人類,我早就猜到你會問了)
所謂一站式框架指的是有EE開發的每一層解決方案。

WEB層 :SpringMVC

Service層 :Spring的Bean管理,Spring聲明式事務

DAO層 :Spring的Jdbc模板,Spring的ORM模塊

1.2為什麼學習Spring

俗話說,人狠話不多(兄嘚看圖)

1.3Spring的版本

Spring3.x、Spring4.x和Spring5.x

1.4Spring的體繫結構

正所謂,人狠話不多(兄嘚看圖)

2、Spring的入門(IOC)

2.1什麼IOC

一說起IOC我就想起了武哥對IOC的理解的幾個例子,可謂通俗易懂,非常適合剛入門Spring的兄嘚!有興趣的可以去了解了解武哥,武哥博客:https://blog.csdn.net/eson_15

IOC(Inverse of Control):控制反轉,也可以稱為依賴倒置。
控制反轉:將對象的創建權反轉給(交給)Spring。

所謂依賴,從程序的角度看,就是比如A要調用B的方法,那麼A就依賴於B,反正A要用到B,則A依

賴於B。所謂倒置,你必須理解如果不倒置,會怎麼著,因為A必須要有B,才可以調用B,如果不倒

置,意思就是A主動獲取B的實例:B b = new B(),這就是最簡單的獲取B實例的方法(當然還有各種

設計模式可以幫助你去獲得B的實例,比如工廠、Locator等等),然後你就可以調用b對象了。所

以,不倒置,意味着A要主動獲取B,才能使用B;到了這裏,就應該明白了倒置的意思了。倒置就是

A要調用B的話,A並不需要主動獲取B,而是由其它人自動將B送上門來。

2.2通俗理解IOC

形象的舉例就是:
通常情況下,假如你有一天在家裡口渴了,要喝水,那麼你可以到你小區的小賣部去,告訴他們,你需要一瓶水,然後小賣部給你一瓶水!這本來沒有太大問題,關鍵是如果小賣部很遠,那麼你必須知道:從你家如何到小賣部;小賣部里是否有你需要的水;你還要考慮是否開着車去;等等等等,也許有太多的問題要考慮了。也就是說,為了一瓶水,你還可能需要依賴於車等等這些交通工具或別的工具,問題是不是變得複雜了?那麼如何解決這個問題呢?
解決這個問題的方法很簡單:小賣部提供送貨上門服務,凡是小賣部的會員,你只要告知小賣部你需要什麼,小賣部將主動把貨物給你送上門來!這樣一來,你只需要做兩件事情,你就可以活得更加輕鬆自在:
第一:向小賣部註冊為會員。
第二:告訴小賣部你需要什麼。

這和Spring的做法很類似!Spring就是小賣部,你就是A對象,水就是B對象
第一:在Spring中聲明一個類:A
第二:告訴Spring,A需要B

假設A是UserAction類,而B是UserService類

<bean id="userService" class="org.leadfar.service.UserService"/>
<bean id="documentService" class="org.leadfar.service.DocumentService"/>
<bean id="orgService" class="org.leadfar.service.OrgService"/>
 
<bean id="userAction" class="org.leadfar.web.UserAction">
     <property name="userService" ref="userService"/>
</bean>

在Spring這個商店(工廠)中,有很多對象/服務:userService,documentService,orgService,也有很多會員:userAction等等,聲明userAction需要userService即可,Spring將通過你給它提供的通道主動把userService送上門來,因此UserAction的代碼示例類似如下所示:

package org.leadfar.web;
public class UserAction{
     private UserService userService;
     public String login(){
          userService.valifyUser(xxx);
     }
     public void setUserService(UserService userService){
          this.userService = userService;
     }
}

在這段代碼裏面,你無需自己創建UserService對象(Spring作為背後無形的手,把UserService對象通過你定義的setUserService()方法把它主動送給了你,這就叫依賴注入!),當然咯,我們也可以使用註解來注入。Spring依賴注入的實現技術是:動態代理

2.3下載Spring的開發包以及解壓說明

官網下載:http://spring.io/
什麼?不會下載?what???
好吧,已打包好了QAQ:https://pan.baidu.com/s/18wyE-5SRWcCu12iPOX56pg
什麼?沒有網盤?what???
有事請燒香謝謝…

解壓之後,文件說明:
docs :Spring的開發規範和API
libs :Spring的開發的jar和源碼
schema :Spring的配置文件的約束

2.4創建web項目,引入jar包

2.5創建普通接口和實現類

創建普通接口,定義一個eat方法

package com.gx.sping;

public interface IUserDao {

    public void eat();
}

創建普通實現類

package com.gx.sping;

public class UserDaoimpl implements IUserDao {
  @Override
    public void eat() {
        // TODO Auto-generated method stub
        System.out.println(用戶eat了");
    }
}

2.6Spring的IOC底層實現原理

創建普通接口和類出現的問題:
如果底層的實現切換了,需要修改源代碼,能不能不修改程序源代碼對程序進行擴展?
重點來了,要想不改變源碼,Spring的IOC就能實現!如下圖:Spring的IOC底層實現

2.7將實現類交給Spring管理

1、在classpath下(也就是src)創建一個XML文件

2、文件名最好統一叫applicationContext.xml

3、其xml文件的內容頭為schema約束

4、約束文件位置在spring的解壓路徑下lspring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.htm

5、不要求xml文件的內容頭能夠背出來,但要了解的是你要知道它是怎麼來的

6、xml文件的內容頭添加后,將實現類交給Spring管理

applicationContext.xml配置文件如下

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">

 <!-- 實現類UserDaoimpl交給Spring管理 -->
     <bean id="IuserDao" class="com.gx.Ioc.UserDaoimpl" ></bean>
</beans>

2.8編寫測試類

package com.gx.Ioc;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpingDemo1 {
    @Test
    public void demo11() {
        // 面向接口傳統方式
        UserDaoimpl userdao = new UserDaoimpl();
        userdao.eat();
    }
       //Spring的bean管理方式
    @Test
    public void demo22() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserDao userdao = (IUserDao) applicationContext.getBean("IuserDao");
        userdao.eat();

    }

}

兄嘚,如果測試不成功最好看看二者是否對應!!!

兄dei,到這裏,Spring的入門(IOC)算是入門了,是不是覺得很有成就感啊?

拉倒吧! 我都不好意思說了.(兄dei,我錯了,是我飄了,呀呀呀,兄dei別打臉鴨QAQ)

但是我依舊是阻止不了你驕傲的心.

那就頂我,讓我感受感受你的驕傲!哈哈哈QAQ

2.9 IOC和DI

IOC不是什麼技術,而是一種設計思想,IOC能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器后,把創建和查找依賴對象的控制權交給了Spring容器,由容器進行注入組合對象,所以對象與對象之間是鬆散耦合,這樣利於功能復用,更重要的是使得程序的整個體繫結構變得非常靈活。
IOC:控制反轉,將對象的創建權反轉給了Spring。
DI:依賴注入,前提必須有IOC的環境,Spring管理這個類的時候將類的依賴的屬性注入(設置)進來。比如說下面講到的Spring的屬性注入其實就是典型的DI

所謂繼承:is a

Class A{

}
Class B extends A{

}

所謂依賴:

Class A{

}
Class B{
    public void xxx(A a){

}
}

所謂聚合:has a

3、Spring的工廠類

3.1Spring工廠類的結構

3.2老版本的工廠類:BeanFactory

BeanFactory:調用getBean的時候,才會生成類的實例。

3.3新版本的工廠類:ApplicationContext

ApplicationContext:加載配置文件的時候,就會將Spring管理的類都實例化
ApplicationContext有兩個實現類
1、ClassPathXmlApplicationContext :加載類路徑下的配置文件
2、FileSystemXmlApplicationContext :加載文件系統下的配置文件

4、Spring的配置

4.1XML的提示配置(Schema的配置)

在XML文件中要使用各種標籤來給spring進行配置,博主我這佩奇腦袋怎麼可能記住spring中所有的標籤呢,不怕不怕,博主我會配置XML的提示配置QAQ,會了這一招就算兄dei你是喬治腦袋也不用擔心(再說了我看兄dei各各英俊瀟洒,玉樹臨風,聰明絕頂…咳咳,暴露了暴露了)

4.2Bean的相關的配置(< bean >標籤的id和name的配置)

id :使用了約束中的唯一約束。裏面不能出現特殊字符的。上面提及到了要與getbean參數值對應

name :沒有使用約束中的唯一約束(理論上可以出現重複的,但是實際開發不能出現的)。裏面可以出現特殊字符。

4.3Bean的生命周期的配置(了解)

init-method :Bean被初始化的時候執行的方法
destroy-method :Bean被銷毀的時候執行的方法(Bean是單例創建,工廠關閉)

4.4Bean的作用範圍的配置(重點)

scope屬性Bean的作用範圍

scope屬性值如下(主要用的是前二者)
singletonscope屬性的默認值,Spring會採用單例模式創建這個對象。
prototype多例模式。(Struts2和Spring整合一定會用到)
request :應用在web項目中,Spring創建這個類以後,將這個類存入到request範圍中。
session :應用在web項目中,Spring創建這個類以後,將這個類存入到session範圍中。
globalsession :應用在web項目中,必須在porlet環境下使用。但是如果沒有這種環境,相對於session。

5、Spring的屬性注入

首先,創建幾個普通類

com.gx.spring.demo.Car
public class Car {
    private String name;
    private Double price;
    
    public Car(String name, Double price) {
        super();
        this.name = name;
        this.price = price;
    }
    @Override
    public String toString() {
        return "Car [name=" + name + ", price=" + price + "]";
    }
}
com.gx.spring.demo.Car2
/**
 * 用作set方法的屬性注入類
 */
public class Car2 {
    private String name;
    private Double price;
    public void setName(String name) {
        this.name = name;
    }
    public void setPrice(Double price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return "Car2 [name=" + name + ", price=" + price + "]";
    }
    
}
com.gx.spring.demo.Person 
/**
 * 用作set方法的對象屬性注入類
 */
public class Person {
    private String name;
    private Car2 car2;
    public void setName(String name) {
        this.name = name;
    }
    public void setCar2(Car2 car2) {
        this.car2 = car2;
    }
    @Override
    public String toString() {
        return "Employee [name=" + name + ", car2=" + car2 + "]";
    }
}

5.1構造方法的方式的屬性注入

構造方法的屬性注入
constructor-arg 標籤用於配置構造方法的屬性注入
name :參數的名稱
value:設置普通數據
ref:引用數據,一般是另一個bean id值

當然了,構造方法的方式的屬性注入也支持對象屬性的注入,標籤中對應屬性也是ref
如果只有一個有參數的構造方法並且參數類型與注入的bean類型匹配,那就會注入到該構造方法中

applicationContext.xml中配置:

<!-- 構造方法的方式 -->
    <bean id="car" class="com.gx.spring.demo.Car">
        <constructor-arg name="name" value="瑪莎拉蒂"/>
        <constructor-arg name="price" value="800000"/>
    </bean>

測試方法:

    /**
     * 構造方法方式的普通屬性注入方法
     */
    public void demo1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Car car = (Car) applicationContext.getBean("car");
        System.out.println(car);
    }

5.2Set方法的方式的屬性注入【開發常用】

Set方法的普通屬性注入
property 標籤用於配置Set方法的屬性注入
name :參數的名稱
value:設置普通數據
ref:引用數據,一般是另一個bean id值

applicationContext.xml中配置:

<!-- set方法的方式 -->
<bean id="car2" class="com.gx.spring.demo.Car2">
        <property name="name" value="法拉利黃金跑車"/>
        <property name="price" value="10000000"/>
</bean> 

測試方法:

@Test
    /**
     * set方法方式的屬性注入
     */
    public void demo2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Car2 car2 = (Car2) applicationContext.getBean("car2");
        System.out.println(car2);
    }

Set方法設置對象類型的屬性
applicationContext.xml中配置:

<!-- set方法注入對象類型的屬性 -->
<bean id="Person" class="com.gx.spring.demo.Person">
            <!-- value:設置普通類型的值,ref:設置其他的類的id或name-->
        <property name="name" value="濤哥"/>
        <property name="car2" ref="car2"/>
    </bean> 

測試方法:

@Test
    /**
     * set方法注入對象類型
     */
    public void demo3(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person= (Person) applicationContext.getBean("Person");
        System.out.println(person);
    }

5.3註解的方式屬性注入【開發常用】

@Component (作用在類上通用:組件)
@Component(“userService”)相當於< bean id=”userService” class=”…”>

衍生:
@Controller Web層
@Service 業務層
@Repository 持久層
這三個註解是為了讓標註類本身的用途清晰

屬性注入的註解 ( 可以沒有set方法
普通類型屬性:@Value

對象類型屬性:@Resource對應bean中的id)= @Autowired(類型)+ @Qualifier(名稱)

5.3.1註解的理解

額,初學框架,註解二字可能對於大部分熊dei來說,太過於陌生,註解其實就是在一個類、方法、屬性上,使用@註解名稱,就比如是我們最熟悉的接實現口中的方法默認會有一個 @Override (熊dei,這樣理解能接受?)

5.3.2註解的jar包導入

Spring3.x註解的jar包
在Spring3.x的版本中,使用註解開發,只需要 spring核心基礎四包外 + log4j包 + 1個依賴包 即可

Spring4.x註解的jar包
然而在Spring4.x版本之後則需在 再添加一個要引入 spring-aop 的 jar 包,因為,spring4.x版本中一些註解的依賴方法封裝在spring-aop 的 jar 包中

5.3.3引入註解的context約束

所謂約束就是就是就是約束啦(搽汗),其中bean約束是最基本的約束!(下圖也可以看出)

引入約束:(引入 context 的約束):

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
5.3.4編寫相關的類
public interface UserDao {
public void sayHello();
}
public class UserDaoImpl implements UserDao {
@Override
public void sayHello() {
System.out.println("Hello Spring...");
} }
5.3.5配置註解掃描

Spring的註解開發:組件掃描(不使用類上註解的時候可以不用組件掃描
使用註解方式,需要開啟組件掃描< context:component-scan base-package=直接包名或者包名.類名/>,當然開發中一般都是base-package=包名,畢竟這樣可以掃描整個包,方便開發
Spring 的註解開發:組件掃描(類上註解: 可以直接使用屬性注入的註解)

<!-- Spring 的註解開發:組件掃描(類上註解: 可以直接使用屬性注入的註解) -->
<context:component-scan base-package="com.gx.spring.demo1"/>
5.3.6 在相關的類上添加註解

1、使用類上註解方式@Component(value=“userDao”),相當於< bean id=”userDao class=”com.gx.類名”>< /bean>
當然value屬性名可以省去直接@Component(”userDao”),當然@Component(“value值任意寫建議取的要有意義”)
2、註解方式可以沒有set方法

@Component(value="userDao")  //相當於配置了<bean id="userDao" class="com.gx.UserDaoImpl "></bean>
public class UserDaoImpl implements UserDao {
@Override
public void sayHello() {
System.out.println("Hello Spring Annotation...");
} }
5.3.7 編寫測試類
@Test
public void demo3() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.sayHello();
}

5.4P名稱空間的屬性注入(Spring2.5以後)

通過引入p名稱空間完成屬性的注入:
寫法:
普通屬性 p:屬性名=”值”
對象屬性 p:屬性名-ref=”值”

P名稱空間的約束引入

使用p名稱空間

5.5 SpEL的屬性注入(Spring3.0以後)

SpEL:Spring Expression Language,Spring的表達式語言。
語法: #{SpEL}

5.6集合類型屬性注入(了解)

集合類型屬性配置:
集合的注入都是在< property>標籤中添加子標籤
數組:< array >
List:< list >
Set:< set >
Map:< map > ,map存放k/v 鍵值對,使用 描述
Properties:< props> < prop key=””>< /prop>
普通數據:< value >
引用數據:< ref >

    <!-- Spring的集合屬性的注入============================ -->
    <!-- 注入數組類型 -->
    <bean id="collectionBean" class="com.gx.spring.demo.CollectionBean">
        <!-- 數組類型 -->
        <property name="arrs">
            <list>
                <value>天才</value>
                <value>王二</value>
                <value>冠希</value>
            </list>
        </property>
        
        <!-- 注入list集合 -->
        <property name="list">
            <list>
                <value>李兵</value>
                <value>趙如何</value>
                <value>鄧鳳</value>
            </list>
        </property>
        
        <!-- 注入set集合 -->
        <property name="set">
            <set>
                <value>aaa</value>
                <value>bbb</value>
                <value>ccc</value>
            </set>
        </property>
        
        <!-- 注入Map集合 -->
        <property name="map">
            <map>
                <entry key="aaa" value="111"/>
                <entry key="bbb" value="222"/>
                <entry key="ccc" value="333"/>
            </map>
        </property>
    </bean>

6、Spring的分模塊開發的配置

分模塊配置:
在加載配置文件的時候,加載多個,沒錯,這就是傳說中的騷操作,堪稱開掛級別的操作(當然,這是可以的不是開掛)

在一個配置文件中引入多個配置文件,簡直優秀!!!

到這裏,恭喜恭喜,各位熊dei以優雅的儀式感闖進Spring世界,對Spring的IOC以及DI有了一定了解了,是不是也很期待Spring的Aop吶,畢竟Spring的核心是控制反轉(IOC)和面向切面(AOP)。

(哎哎,別打..別打..別打臉….)

如果本文對你有一點點幫助,就請點個讚唄,手留余香,謝謝!

最後,歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…

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

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

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

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

[學習筆記] 在Eclipse中使用Hibernate,並創建第一個Demo工程,數據庫為Oracle XE

前文參考:

在Eclipse中使用Hibernate

安裝 Hibernate Tools 插件

https://tools.jboss.org/downloads/

Add the following URL to your Eclipse 4.13 (2019-09) installation, via:

Help > Install New Software… > Work with:

http://download.jboss.org/jbosstools/photon/stable/updates/

Then select the individual features that you want to install:

點擊Next

點擊Next
同意相關協議,點擊Finish .

則會開始下載安裝。

視網絡速度,可能需要幾分鐘到十幾分鐘的時間才能完成安裝。

最後會提示重啟Eclipse才能生效。

在Eclipse中新建Hibernate應用

File->New -> Java Project

點擊Finish

項目結構圖

在Eclipse中新建用戶庫

此時下面显示了已經建立的用戶庫列表

我們要添加Hibernate的依賴庫,因此點擊用戶庫

Hibernate_4.3.5_final

選擇jar文件

項目結構圖

繼續配置Hibernate

最後自動形成 如下的文件內容:[本例使用oracle數據庫]

Oracle 11g xe 在windows安裝請看如下鏈接:

hibernate.cfg.xml

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="hibernate.connection.password">123456</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
        <property name="hibernate.connection.username">test</property>
        <property name="hibernate.default_schema">test</property>
        <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
    </session-factory>
</hibernate-configuration>

再增加幾個屬性

配置文件更新后的內容如下: 注意要去掉name屬性 更改 為

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                         "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
 <session-factory >
  <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
  <property name="hibernate.connection.password">123456</property>
  <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:xe:orcl</property>
  <property name="hibernate.connection.username">test</property>
  <property name="hibernate.default_schema">test</property>
  <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
  <property name="hibernate.show_sql">true</property>
  <!-- 第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等 應用第一次運行起來后才會。 -->
  <property name="hibernate.hbm2ddl.auto">update</property> 
  <property name="hibernate.format_sql">true</property>
  <property name="hibernate.default_entity_mode">pojo</property>
 </session-factory>
</hibernate-configuration>

繼續完善工程Hibernate_demo_001

新建一個包:mytest001.demo

在包mytest001.demo之下新建一個PO類: Emp

package mytest001.demo;

public class Emp {
    // 員工的標識屬性
    private Integer id;
    // 姓名
    private String name;
    // 年齡
    private Integer age;
    // 工資 (分)
    private Integer salary;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSalary() {
        return salary;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Emp [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
    }
}

此刻此PO Emp.java 尚不具備持久化能力。下面為其添加註解。

@Entity 註解聲明該類是一個Hibernate持久化類
@Table 指定該類映射的表,對應的數據庫表名是T_EMP
@Id 指定該類的標識屬性,映射到數據庫的主鍵列
@GeneratedValue(strategy=GenerationType.SEQUENCE) 指定了主鍵生成策略,由於本文使用Oracle Database, 因此指定了使用 SEQUENCE

在hibernate.cfg.xml中增加持久化映射類名

增加一個新類:EmpManager,用於管理員工。

package mytest001.demo;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.Service;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;


public class EmpManager {

    public static void main(String[] args)  throws Exception  {
         
    //實例化配置
    Configuration configuration  = new Configuration()
            //不帶參數則默認加載hibernate.cfg.xml
            .configure();
    
    ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
            .applySettings(configuration.getProperties()).build();
    SessionFactory sFactory = configuration.buildSessionFactory(serviceRegistry);
    
    //創建session 
    Session session = sFactory.openSession();
    
    //開始事務
    Transaction tx = session.beginTransaction();
    //創建員工對象
    Emp emp = new Emp();
    // 設置員工信息
    emp.setAge(28);
    emp.setName("scott");
    emp.setSalary(10000);
    session.save(emp);
    // 提交事務
    tx.commit();
    session.close();
    sFactory.close();
            
    
    }

}

最後配置文件的內容:hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                         "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
 <session-factory >
  <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
  <property name="hibernate.connection.password">123456</property>
  <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
  <property name="hibernate.connection.username">test</property>
  <property name="hibernate.default_schema">test</property>
  <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
  <property name="hibernate.show_sql">true</property>
  <!-- 第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等 應用第一次運行起來后才會。 -->
  <property name="hibernate.hbm2ddl.auto">update</property> 
  <property name="hibernate.format_sql">true</property>
  <property name="hibernate.default_entity_mode">pojo</property>
  <mapping class="mytest001.demo.Emp"/>
 </session-factory>
</hibernate-configuration>

Emp.java

package mytest001.demo;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="T_EMP")
public class Emp {
    // 員工的標識屬性
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    private Integer id;
    // 姓名
    private String name;
    // 年齡
    private Integer age;
    // 工資 (分)
    private Integer salary;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSalary() {
        return salary;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Emp [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
    }
}

最後的工程結構如下:

運行EmpManger

十一月 23, 2019 8:50:47 上午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.4.Final}
十一月 23, 2019 8:50:47 上午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.3.5.Final}
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration configure
INFO: HHH000043: Configuring from resource: /hibernate.cfg.xml
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration getConfigurationInputStream
INFO: HHH000040: Configuration resource: /hibernate.cfg.xml
十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration doConfigure
INFO: HHH000041: Configured SessionFactory: null
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH000402: Using Hibernate built-in connection pool (not for production use!)
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000401: using driver [oracle.jdbc.driver.OracleDriver] at URL [jdbc:oracle:thin:@localhost:1521:xe]
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000046: Connection properties: {user=test, password=****}
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000006: Autocommit mode: false
十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
十一月 23, 2019 8:50:48 上午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.Oracle10gDialect
十一月 23, 2019 8:50:48 上午 org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
十一月 23, 2019 8:50:48 上午 org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: T_EMP
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: T_EMP
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: T_EMP
十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Hibernate: 
    select
        test.hibernate_sequence.nextval 
    from
        dual
Hibernate: 
    insert 
    into
        test.T_EMP
        (age, name, salary, id) 
    values
        (?, ?, ?, ?)
十一月 23, 2019 8:50:48 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:oracle:thin:@localhost:1521:xe]

到數據庫中查詢表:(這個表會被自動創建)
select * from t_emp;

如果再次運行會增加新的記錄。

至此,本文完成。

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

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

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

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

Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis

在 Redis 出現之前,我們的緩存框架各種各樣,有了 Redis ,緩存方案基本上都統一了,關於 Redis,松哥之前有一個系列教程,尚不了解 Redis 的小夥伴可以參考這個教程:

使用 Java 操作 Redis 的方案很多,Jedis 是目前較為流行的一種方案,除了 Jedis ,還有很多其他解決方案,如下:

除了這些方案之外,還有一個使用也相當多的方案,就是 Spring Data Redis。

在傳統的 SSM 中,需要開發者自己來配置 Spring Data Redis ,這個配置比較繁瑣,主要配置 3 個東西:連接池、連接器信息以及 key 和 value 的序列化方案。

在 Spring Boot 中,默認集成的 Redis 就是 Spring Data Redis,默認底層的連接池使用了 lettuce ,開發者可以自行修改為自己的熟悉的,例如 Jedis。

Spring Data Redis 針對 Redis 提供了非常方便的操作模板 RedisTemplate 。這是 Spring Data 擅長的事情,那麼接下來我們就來看看 Spring Boot 中 Spring Data Redis 的具體用法。

方案一:Spring Data Redis

創建工程

創建工程,引入 Redis 依賴:

創建成功后,還需要手動引入 commos-pool2 的依賴,因此最終完整的 pom.xml 依賴如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
</dependencies>

這裏主要就是引入了 Spring Data Redis + 連接池。

配置 Redis 信息

接下來配置 Redis 的信息,信息包含兩方面,一方面是 Redis 的基本信息,另一方面則是連接池信息:

spring.redis.database=0
spring.redis.password=123
spring.redis.port=6379
spring.redis.host=192.168.66.128
spring.redis.lettuce.pool.min-idle=5
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=1ms
spring.redis.lettuce.shutdown-timeout=100ms

自動配置

當開發者在項目中引入了 Spring Data Redis ,並且配置了 Redis 的基本信息,此時,自動化配置就會生效。

我們從 Spring Boot 中 Redis 的自動化配置類中就可以看出端倪:

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
                    RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
    }
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(
                    RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
    }
}

這個自動化配置類很好理解:

  1. 首先標記這個是一個配置類,同時該配置在 RedisOperations 存在的情況下才會生效(即項目中引入了 Spring Data Redis)
  2. 然後導入在 application.properties 中配置的屬性
  3. 然後再導入連接池信息(如果存在的話)
  4. 最後,提供了兩個 Bean ,RedisTemplate 和 StringRedisTemplate ,其中 StringRedisTemplate 是 RedisTemplate 的子類,兩個的方法基本一致,不同之處主要體現在操作的數據類型不同,RedisTemplate 中的兩個泛型都是 Object ,意味者存儲的 key 和 value 都可以是一個對象,而 StringRedisTemplate 的 兩個泛型都是 String ,意味者 StringRedisTemplate 的 key 和 value 都只能是字符串。如果開發者沒有提供相關的 Bean ,這兩個配置就會生效,否則不會生效。

使用

接下來,可以直接在 Service 中注入 StringRedisTemplate 或者 RedisTemplate 來使用:

@Service
public class HelloService {
    @Autowired
    RedisTemplate redisTemplate;
    public void hello() {
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("k1", "v1");
        Object k1 = ops.get("k1");
        System.out.println(k1);
    }
}

Redis 中的數據操作,大體上來說,可以分為兩種:

  1. 針對 key 的操作,相關的方法就在 RedisTemplate 中
  2. 針對具體數據類型的操作,相關的方法需要首先獲取對應的數據類型,獲取相應數據類型的操作方法是 opsForXXX

調用該方法就可以將數據存儲到 Redis 中去了,如下:

k1 前面的字符是由於使用了 RedisTemplate 導致的,RedisTemplate 對 key 進行序列化之後的結果。

RedisTemplate 中,key 默認的序列化方案是 JdkSerializationRedisSerializer 。

而在 StringRedisTemplate 中,key 默認的序列化方案是 StringRedisSerializer ,因此,如果使用 StringRedisTemplate ,默認情況下 key 前面不會有前綴。

不過開發者也可以自行修改 RedisTemplate 中的序列化方案,如下:

@Service
public class HelloService {
    @Autowired
    RedisTemplate redisTemplate;
    public void hello() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("k1", "v1");
        Object k1 = ops.get("k1");
        System.out.println(k1);
    }
}

當然也可以直接使用 StringRedisTemplate:

@Service
public class HelloService {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    public void hello2() {
        ValueOperations ops = stringRedisTemplate.opsForValue();
        ops.set("k2", "v2");
        Object k1 = ops.get("k2");
        System.out.println(k1);
    }
}

另外需要注意 ,Spring Boot 的自動化配置,只能配置單機的 Redis ,如果是 Redis 集群,則所有的東西都需要自己手動配置,關於如何操作 Redis 集群,松哥以後再來和大家分享。

方案二:Spring Cache

通過 Spring Cache 的形式來操作 Redis,Spring Cache 統一了緩存江湖的門面,這種方案,松哥之前有過一篇專門的文章介紹,小夥伴可以移步這裏:。

方案三:回歸原始時代

第三種方案,就是直接使用 Jedis 或者 其他的客戶端工具來操作 Redis ,這種方案在 Spring Boot 中也是支持的,雖然操作麻煩,但是支持,這種操作松哥之前也有介紹的文章,因此這裏就不再贅述了,可以參考 。

總結

Spring Boot 中,Redis 的操作,這裏松哥給大家總結了三種方案,實際上前兩個使用廣泛一些,直接使用 Jedis 還是比較少,基本上 Spring Boot 中沒見過有人直接這麼搞。

好了,本文就說到這裏,有問題歡迎留言討論。

相關案例已經上傳到 GitHub,歡迎小夥伴們們下載:

掃碼關注松哥,公眾號後台回復 2TB,獲取松哥獨家 超2TB 免費 Java 學習乾貨

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

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

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

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

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

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

javascript閉包詳解

閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包實現。

下面就是我的學習筆記,對於Javascript初學者應該是很有用的。

一、變量的作用域

要理解閉包,首先必須理解Javascript特殊的變量作用域。

變量的作用域無非就是兩種:全局變量和局部變量。

Javascript語言的特殊之處,就在於函數內部可以直接讀取全局變量。

  

var n=999;

  function f1(){
    alert(n);
  }

  f1(); // 999

  

另一方面,在函數外部自然無法讀取函數內的局部變量。

 

 function f1(){
    var n=999;
  }

  alert(n); // error

  

這裡有一個地方需要注意,函數內部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!

 

 function f1(){
    n=999;
  }

  f1();

  alert(n); // 999

  

二、如何從外部讀取局部變量?

出於種種原因,我們有時候需要得到函數內的局部變量。但是,前面已經說過了,正常情況下,這是辦不到的,只有通過變通方法才能實現。

那就是在函數的內部,再定義一個函數。

  

function f1(){

    var n=999;

    function f2(){
      alert(n); // 999
    }

  }

  

在上面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2都是可見的。但是反過來就不行,f2內部的局部變量,對f1就是不可見的。這就是Javascript語言特有的”鏈式作用域”結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。

既然f2可以讀取f1中的局部變量,那麼只要把f2作為返回值,我們不就可以在f1外部讀取它的內部變量了嗎!

 

 function f1(){

    var n=999;

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  

三、閉包的概念

上一節代碼中的f2函數,就是閉包。

各種專業文獻上的”閉包”(closure)定義非常抽象,很難看懂。我的理解是,閉包就是能夠讀取其他函數內部變量的函數。

由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成”定義在一個函數內部的函數”。

所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。

四、閉包的用途

閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。

怎麼來理解這句話呢?請看下面的代碼。

  function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數f1中的局部變量n一直保存在內存中,並沒有在f1調用后被自動清除。

為什麼會這樣呢?原因就在於f1是f2的父函數,而f2被賦給了一個全局變量,這導致f2始終在內存中,而f2的存在依賴於f1,因此f1也始終在內存中,不會在調用結束后,被垃圾回收機制(garbage collection)回收。

這段代碼中另一個值得注意的地方,就是”nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個匿名函數本身也是一個閉包,所以nAdd相當於是一個setter,可以在函數外部對函數內部的局部變量進行操作。

五、使用閉包的注意點

1)由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。

2)閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。

六、思考題

如果你能理解下面兩段代碼的運行結果,應該就算理解閉包的運行機制了。

代碼片段一。

  var name = “The Window”;

  var object = {
    name : “My Object”,

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());

代碼片段二。

  var name = “The Window”;

  var object = {
    name : “My Object”,

    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };

    }

  };

  alert(object.getNameFunc()());

 

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

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

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

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

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

PowerMock學習(四)之Mock static的使用

我們編寫代碼的時候,總會寫一些工具類,為了方便調用喜歡使用static關鍵字來修飾對應方法。

那麼現在舉例說明,還是準備兩個接口,第一個是查詢學生總數,第二個是新增學生兩個接口,具體示例代碼如下:

package com.rongrong.powermock.mockstatic;

import com.rongrong.powermock.service.Student;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/23 8:08
 */
public class StudentStaticService {

    /**
     * 獲取學生總數
     * @return
     */
    public int getStudentTotal(){
        return StudentUtils.getStudent();
    }

    /**
     * 創建一個學生
     * @param student
     */
    public void createStudent(Student student){
        StudentUtils.createStudent(student);
    }
}

接着我們再來看看這個靜態工具類StudentUtils,具體代碼示例如下:

package com.rongrong.powermock.mockstatic;

import com.rongrong.powermock.service.Student;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/23 7:38
 */
public class StudentUtils {
    /**
     * 獲取學生總數
     * @return
     */
    public static int getStudent(){
        throw new UnsupportedOperationException();
    }

    /**
     * 創建一個學生
     * @param student
     */
    public static void createStudent(Student student){
        throw new UnsupportedOperationException();
    }
}

接下來我們用傳統方式,來做單元測試,示例代碼如下:

    @Test
    public void testGetStudnetTotal(){
        StudentStaticService staticService = new StudentStaticService();
        int studentTotal = staticService.getStudentTotal();
        assertEquals(studentTotal,10);
    }

    @Test
    public void testCreateStudent(){
        StudentStaticService staticService = new StudentStaticService();
        staticService.createStudent(new Student());
        assertTrue(true);
    }

接着運行下測試用例,結果肯定報錯了,為什麼報錯,這裏就不再細說了,參考之前文章,報錯,如下圖所示:

 

接下來我們使用powermock來進行測試,具體示例代碼如下:

 @Test
    public void testGetStudentWithMock(){
        //先mock工具類對象
        PowerMockito.mockStatic(StudentUtils.class);
        //模擬靜態類調用
        PowerMockito.when(StudentUtils.getStudent()).thenReturn(10);
        //構建service
        StudentStaticService service = new StudentStaticService();
        int studentTotal = service.getStudentTotal();
        assertEquals(10,studentTotal);
    }

    @Test
    public void testCreateStudentWithMock(){
        //先模擬靜態工具類
        PowerMockito.mockStatic(StudentUtils.class);
        //模擬調用
        PowerMockito.doNothing().when(StudentUtils.class);
        //構建service
        StudentStaticService service = new StudentStaticService();
        Student student = new Student();
        service.createStudent(student);
        //這裏用powermock來驗證,而不是mock,更體現了powermock的強大
        PowerMockito.verifyStatic();
    }

再次運行,測試通過,如下圖所示:

 

 

運行之前先讓powermock為我們準備了StudentUtils工具類,而且採用mockstatic的方法,最後我們用powermock.verifyStatic()驗證,而不是mock,更體現了powermock的強大。

 

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

阿里雲ECS服務器部署HADOOP集群(一):Hadoop完全分佈式集群環境搭建,阿里雲ECS服務器部署HADOOP集群(二):HBase完全分佈式集群搭建(使用外置ZooKeeper),阿里雲ECS服務器部署HADOOP集群(三):ZooKeeper 完全分佈式集群搭建,阿里雲ECS服務器部署HADOOP集群(四):Hive本地模式的安裝,阿里雲ECS服務器部署HADOOP集群(六):Flume 安裝,阿里雲ECS服務器部署HADOOP集群(七):Sqoop 安裝

準備:

兩台配置CentOS 7.3的阿里雲ECS服務器;

hadoop-2.7.3.tar.gz安裝包;

jdk-8u77-linux-x64.tar.gz安裝包;

hostname及IP的配置:

更改主機名:

由於系統為CentOS 7,可以直接使用‘hostnamectl set-hostname 主機名’來修改,修改完畢后重新shell登錄或者重啟服務器即可。

1 hostnamectl set-hostname master
2 exit
3 ssh root@master
1 hostnamectl set-hostname slave1
2 exit 3 ssh root@slave1

設置本地域名:

設置本地域名這一步非常關鍵,ip的本地域名信息配置不好,即有可能造成Hadoop啟動出現問題,又有可能造成在使用Hadoop的MapReduce進行計算時報錯。在ECS上搭建Hadoop集群環境需參考以下兩篇文章:

總結一下那就是,在“/etc/hosts”文件中進行域名配置時要遵從2個原則:

  •  新加域名在前面: 將新添加的Master、Slave服務器ip域名(例如“test7972”),放置在ECS服務器原有本地域名(例如“iZuf67wb***************”)的前面。但是注意ECS服務器原有本地      域名(例如“iZuf67wb***************”)不能被刪除,因為操作系統別的地方還會使用到。
  •  IP本機內網,其它外網: 在本機上的操作,都要設置成內網ip;其它機器上的操作,要設置成外網ip。

master

slave1

此處摘自 

配置好后需要在各個節點上執行如下命令,測試是否相互 ping 得通,如果 ping 不通,後面就無法順利配置成功:

1 ping master -c 3
2 ping slave1 -c 3

例如我在 master 節點上 ping slave1 ,ping 通的話會显示 time,显示的結果如下圖所示:

各節點角色分配

master: NameNode  ResourceManager

slave1: DataNode NodeManager

免密碼登錄配置

分別在 master 和 slave1 上做如下操作

1 ssh-keygen -t rsa
2 ssh-copy-id master 3 ssh-copy-id slave1

驗證

ssh master date;ssh slave1 date

配置JDK

解壓JDK安裝包到/usr/local/下

tar -zxvf jdk-8u77-linux-x64.tar.gz -C /usr/local/

將解壓目錄改為 jdk1.8

mv jdk1.8.0_77/ jdk1.8/

設置JAVA_HOME到系統環境變量

vim /etc/profile

在最後加入以下兩行代碼

export JAVA_HOME=/usr/local/jdk1.8
export PATH=$PATH:$JAVA_HOME/bin

重新加載環境

source /etc/profile

這樣 master 的jdk就配置好了,可以用命令 java -version 測試下。

java -version

下面只需將 master 上配置好的文件分發到 slave1 上即可。

將/usr/local/jdk1.8分發到 slave1 的/usr/local/下(建議壓縮后再分發)

scp -r /usr/local/jdk1.8/ slave1:/usr/local/

將/etc/profile分發到 slave1 的/etc/下

scp /etc/profile slave1:/etc/

  然後重新加載 slave1 環境便完成了 slave1 的jdk配置

source /etc/profile

hadoop集群配置

1 cd ~
2 tar -zxvf hadoop-2.7.3.tar.gz -C /usr/local # 解壓到/usr/local中
3 cd /usr/local/
4 mv ./hadoop-2.7.3/ ./hadoop            # 將文件夾名改為hadoop

輸入如下命令來檢查 Hadoop 是否可用,成功則會显示 Hadoop 版本信息:

1 cd /usr/local/hadoop
2 ./bin/hadoop version

添加 HADOOP_HOME 到系統環境變量

vim /etc/profile

在後面添加如下兩行

1 export HADOOP_HOME=/usr/local/hadoop
2 export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

重新加載環境,並輸出變量 HADOOP_HOME 驗證

進入/user/local/hadoop/etc/hadoop/可以看到如下配置文件

集群/分佈式模式需要修改 /usr/local/hadoop/etc/hadoop 中的6個配置文件,更多設置項可點擊查看官方說明,這裏僅設置了我完成課堂作業所必須的設置項:hadoop-env.sh, slaves,  ,  ,  ,   。

1.首先來配置 hadoop-env.sh ,只需要設置一下JAVA_HOME即可

注:之前在配置jdk中配置的是基於系統的JAVA_HOME變量,這裏需要配置基於Hadoop集群的JAVA_HOME變量。

hadoop-env.sh 是Hadoop的環境變量配置腳本。

所以應做以下修改 vim hadoop-env.sh

export JAVA_HOME=/usr/local/jdk1.8

2.配置 slave , 指定 slave 節點

sudo vi slaves

刪去原有的 localhost , 添加將作為 slave 節點的 slave1

3.配置 core-site.xml 

 1 <configuration>
 2 
 3     <property>
 4         <name>fs.defaultFS</name>
 5         <value>hdfs://master:9000</value>
 6         <description>The name of the default file system.</description>
 7     </property> 
 8 # 設置訪問hdfs的默認名,9000是默認端口
 9 
10     <property>
11         <name>hadoop.tmp.dir</name>
12         <value>/usr/local/hadoop/tmp</value>
13         <description>Abase for other temporary directories.</description>
14     </property>
15 # 在hdfs格式化的時候會自動創建相應的目錄 'tmp/' 16 17 <property> 18 <name>fs.trash.interval</name> 19 <value>4320</value> 20 <description>Number of minutes after which the checkpoint gets deleted.</description> 21 </property> 22 # 設置回收站里的文件保留時間(單位:秒) 23 24 </configuration>

4.配置 hdfs-site.xml 

 1 <configuration>
 2 
 3     <property>
 4         <name>dfs.namenode.name.dir</name>
 5         <value>/usr/local/hadoop/tmp/dfs/name</value>
 6     </property>
 7 
 8     <property>
 9         <name>dfs.datanode.data.dir</name>
10         <value>/usr/local/hadoop/tmp/dfs/data</value>
11     </property>
12 
13     <property>
14         <name>dfs.replication</name>
15         <value>1</value>
16     </property>
17 # 副本,因為有一個 slave 節點這裏設置為1(一般偽分佈模式設1個,三個或三個以上節點設3個)
18 
19     <property>
20         <name>dfs.permissions.enabled</name>
21         <value>false</value>
22         <description>If "true", enable permission checking in HDFS. If "false", permission checking is turned off, but all other behavior is unchanged. Switching from one parameter value to the other does not change the mode, owner or group of files or directories.</description>
23     </property>
24 
25 </configuration>    

5.配置 mapred-site.xml (這個文件沒有直接提供,而是提供了模版文件,需將模版文件轉換為配置文件) 

1 sudo mv mapred-site.xml.template mapred-site.xml
2 sudo vi mapred-site.xml
 1 <configuration>
 2 
 3     <property>
 4         <name>mapreduce.framework.name</name>
 5         <value>yarn</value>
 6         <description>The runtime framework for executing MapReduce jobs.Can be one of local, classic or yarn.</description>
 7     </property>
 8     <property>
 9         <name>mapreduce.jobtracker.http.address</name>
10         <value>master:50030</value>
11     </property>
12     <property>
13         <name>mapreduce.jobhisotry.address</name>
14         <value>master:10020</value>
15     </property>
16     <property>
17         <name>mapreduce.jobhistory.webapp.address</name>
18         <value>master:19888</value>
19     </property>
20     <property>
21         <name>mapreduce.jobhistory.done-dir</name>
22         <value>/jobhistory/done</value>
23     </property>
24     <property>
25         <name>mapreduce.jobhistory.intermediate-done-dir</name>
26         <value>/jobhisotry/done_intermediate</value>
27     </property>
28     <property>
29         <name>mapreduce.job.ubertask.enable</name>
30         <value>true</value>
31         <description>Whether to enable the small-jobs "ubertask" optimization,which runs "sufficiently small" jobs sequentially within a single JVM."Small" is defined by the following maxmaps, maxreduces, and maxbytes settings. Note that configurations for application masters also affect the "Small" definition - yarn.app.mapreduce.am.resource.mb must be larger than both mapreduce.map.memory.mb and mapreduce.reduce.memory.mb, and yarn.app.mapreduce.am.resource.cpu-vcores must be larger than both mapreduce.map.cpu.vcores and mapreduce.reduce.cpu.vcores to enable ubertask. Users may override this value.</description>
32     </property>
33 
34 </configuration>

6.配置 yarn-site.xml

 1 <configuration>
 2 
 3     <property>
 4         <name>yarn.resourcemanager.hostname</name>
 5         <value>master</value>
 6     </property>
 7     <property>
 8         <name>yarn.nodemanager.aux-services</name>
 9         <value>mapreduce_shuffle</value>
10         <description>A comma separated list of services where service name should only contain a-zA-Z0-9_ and can not start with numbers</description>
11     </property>
12     <property>
13         <name>yarn.resourcemanager.address</name>
14         <value>master:18040</value>
15     </property>
16     <property>
17         <name>yarn.resourcemanager.scheduler.address</name>
18         <value>master:18030</value>
19     </property>
20     <property>
21         <name>yarn.resourcemanager.resource-tracker.address</name>
22         <value>master:18025</value>
23     </property>
24     <property>
25         <name>yarn.resourcemanager.admin.address</name>
26         <value>master:18141</value>
27     </property>
28     <property>
29         <name>yarn.resourcemanager.webapp.address</name>
30         <value>master:18088</value>
31     </property>
32     <property>
33         <name>yarn.log-aggregation-enable</name>
34         <value>true</value>
35     </property>
36     <property>
37         <name>yarn.log-aggregation.retain-seconds</name>
38         <value>86400</value>
39     </property>
40     <property>
41         <name>yarn.log-aggregation.retain-check-interval-seconds</name>
42         <value>86400</value>
43     </property>
44     <property>
45         <name>yarn.nodemanager.remote-app-log-dir</name>
46         <value>/tmp/logs</value>
47     </property>
48     <property>
49         <name>yarn.nodemanager.remote-app-log-dir-suffix</name>
50         <value>logs</value>
51     </property>
52 
53 </configuration>

 到這裏 master 就已經配置好了,下面將該服務器的配置分發到 slave1 上去(建議壓縮后再分發),在此使用壓縮後分發的方法

在 master 節點上執行

1 cd /usr/local
2 tar -zcvf ~/hadoop.master.tar.gz ./hadoop 3 cd ~ 4 scp ./hadoop.master.tar.gz slave1:/root/ 5 scp /etc/profile slave1:/etc/

在 slave1 節點上執行

tar -zxvf ~/hadoop.master.tar.gz -C /usr/local

在 slave1 上重新加載環境並檢查驗證

source /etc/profile
echo $HADOOP_HOME

HDFS NameNode 格式化(只要在 master 上執行即可)

$HADOOP_HOME/bin/hdfs namenode -format

看到下面的輸出,表明hdfs格式化成功

INFO common.Storage: Storage directory /usr/local/hadoop/tmp/dfs/name has been successfully formatted.

啟動前檢查防火牆狀態

systemctl status firewalld

我這裡是已經關閉的,若未關閉,可以參考下圖(來自)

阿里雲服務器還需要在服務器安全組裡配置防火牆,需將配置文件里的相關端口全部添加,否則會出現 web 頁面打不開,以及 DataNode 啟動但 Live datenode 為 0 等問題

啟動 Hadoop 集群

$HADOOP_HOME/sbin/start-all.sh

 

  • 第一次啟動 hadoop 時會出現 ssh 提示,提示是否要連入 0.0.0.0 節點,輸入 yes 即可
  • 若出現 hadoop 啟動時 datanode 沒有啟動,可以參考來解決

啟動 job history server

在 master 上執行

$HADOOP_HOME/sbin/mr-jobhistory-daemon.sh start historyserver

成功后在兩個節點上驗證

在 master 上 執行 

jps

可以看到 ResourceManager、SecondaryNameNode、NameNode、JobHistoryServer 四個進程全部啟動

在 slave1 上執行

jps

可以看到 NodeManager、DataNode 兩個進程全部啟動

缺少任一進程都表示出錯。另外還需要在 Master 節點上通過命令 hdfs dfsadmin -report 查看 DataNode 是否正常啟動,如果 Live datanodes 不為 0 ,則說明集群啟動成功。例如我這邊一共有 1 個 Datanodes:

全部配置完成之後查看 web 頁面

hdfs: http://master:50070/

hdfs Datanode 節點信息

hdfs 的情況

hdfs 的文件情況

 yarn:http://master:18088/

 

阿里雲ECS服務器部署HADOOP集群系列:

 

 

 

 

 

 

 

 

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

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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