面向對象再探究

在還不清楚怎樣面向對象?一文中,已經簡單介紹了面向對象的基本思想和三大特性,但是不夠詳細。本文再來具體探究一下面向對象。

1. 概述

1.1. 面向過程編程

面向過程編程(Procedure Oriented Programming,POP)是一種以過程為中心的編程思想,開發人員在解決問題時更專註於過程。

當我們遇到一個問題時,只需要分析出第1步要做什麼、第2步要做什麼……直到解決問題,然後把這些步驟一步步地實現即可。

比如,我現在要編寫一個面向過程的程序來模擬「我要讓我的好朋友行小觀去幫我買瓶水」這個問題,如下:

  1. 給行小觀10塊錢
  2. 告訴行小觀去哪買水
  3. 行小觀找到我要的那瓶水
  4. 付錢,找零錢
  5. 把水帶回來給我

在從「給行小觀錢」到「行小觀把水給我」的整個過程,我和行小觀都專註於完成每一個步驟(事件),行小觀只是一個執行我事先描述好的步驟的無思想的工具人而已。

如果我還有其他問題需要行小觀幫忙,那麼我就還得把如何完成這些問題的詳細步驟全都告訴他,這麼麻煩那我還找別人幫忙干什麼呢?還不如我自己去做。而且有些問題我自己也不會做,那怎麼辦?所以行小觀並不是一個合格的工具人。

1.2. 面向對象編程

與面向過程編程不同,面向對象編程(Object Oriented Programming,OOP)是一種以對象為中心的編程思想,對象是現實世界中的一個個事物的實體。

對象包含了用戶可以使用(公開)的功能部分和對用戶隱藏的實現部分。

在開發過程中,我們可以使用功能部分來解決問題,但是並不關心功能是怎樣實現的。

還是上面的那個買水的例子,使用面向對象來實現:

  1. 我給行小觀10塊錢,讓他幫我買瓶水
  2. 行小觀把水和零錢帶回來給我

在這個例子中,我只需要給行小觀錢,然後他就能幫我買水。我相信我請行小觀有「買水的能力」,他一定能幫我買到水。至於去哪買?怎麼買?行小觀自己知道,我並不關心他是怎麼買到水的,因為我的目的很簡單:「我想要一瓶水」。

如果我有其他問題需要行小觀幫忙,無論這些問題我會不會做,直接告訴他就好了,他會幫我完成。

我只專註於問題本身,具體的操作我並不關心。現在行小觀是一個合格的工具人了。

2. 類和對象

2.1. 二者之間的關係

這裏通過一個大家耳熟能詳的神話——女媧造人,來說明類和對象之間的關係。

相傳女媧以泥土仿照自己摶土造人,創造並構建人類社會。

在這個神話里,「女媧」是一個藍圖、模板,「人」是依據該藍圖被創造出來的個體。

「女媧」可以看做類(Class),「人」可以看做對象(Object)。

跳出神話,來到真實世界。

我們目能所及的事物都可以看做是「對象」,比如說你用的桌子、坐的椅子、玩的電腦、養的狗……這些一個個真實存在,你能摸到的物品都是對象。

狗有千千萬……高的、矮的、胖的、瘦的、黑色的、白色的等各不相同,但是總能在這些不同的狗之中找到相同的特性,這些不同品種的狗我們把它統稱為「狗」。「狗」即為類,而我們養的真實存在的狗為對象。

總結一下:

  • 類是對一類具有共同特徵的事物的抽象,是一類事物的統稱,是一個抽象概念(比如“人類”這個名詞)。

  • 對象是這類事物相對應的具體存在的實體,是一個具體存在的事物(比如“行小觀”這個具體的人)。

  • 類是創建單個對象時的藍圖、模板。

2.2. 類

當我們說到「狗」這個類的時候,會很自然地想到和狗相關的一些特點和習性。

比如,名字、品種、顏色、年齡等,這些是屬性。

還有,吠叫、看門等,這些是行為。

一個類包括了屬性和行為,行為可以操縱屬性。

對應到代碼中,屬性即為成員變量,行為即為成員方法,成員方法可以操縱成員變量。

下面是一個具體的類:

程序2-1
/**
 * 狗類
 * @author Xing Xiaoguan
 */

public class Dog {
    //屬性——成員變量
    String name;
    int age;
    int legs;

    //行為——成員方法
    //行為——吠叫
    public void say() {
        System.out.println("我是" + name + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }
}

2.3. 對象

類是一個抽象概念,而對象則是一個具體的實例。

以狗為例,我們養的不可能是「一類狗」,而是在和一隻「具體的狗」玩耍,比如說哮天犬。

回到女媧(類)造人(對象)這個神話中,人是以女媧為模板被造出來的,女媧造人的過程,即由類構造對象的過程稱為創建類的實例(instance)。

程序2-2
public static void main(String[] args) {
   Dog dog = new Dog();//創建類的實例,對象dog
   dog.name = "哮天犬";
   dog.age = 2;
   dog.legs = 4;
   dog.say();
}

對於被創建出來的對象而言,它們都不一樣,每一個特定的對象(實例)都有一組特定的屬性值(成員變量),這些屬性值的集合就是這個對象的當前狀態,只要對象使用者通過行為(成員方法)向該對象發送消息,這些狀態就可能被改變。

上面這句話怎麼理解?

現在有兩隻狗(兩個對象):哮天犬和哮地犬,這兩個對象的名字、年齡等屬性不同,即當前狀態不同。每隻狗都有一個行為:可以「每過一年,年齡增長1歲」,當通過該行為向哮天犬發送消息時,哮天犬的狀態就被改變了。

可以看出,一個對象由狀態(state)和行為(behavior)組成,對象在成員變量中存儲狀態,通過成員方法公開其行為。

研究一個對象,我們要去關注它處於什麼狀態?具有哪些行為?

對象的三個主要特性:

  • 對象的行為——可以對對象施加哪些操作(方法)?
  • 對象的狀態——當施加那些方法時,對象如何響應?
  • 對象標識——如何辨別具有相同狀態與行為的不同對象?

3. 三大特性

3.1. 封裝

封裝(encapsulation)是Java面向對象的三大特行之一。

一個對象具有屬性和行為,封裝把其屬性和行為組合在了一起,但是為什麼需要封裝?

上文已經介紹了,一個對象由其狀態和行為組成。我們回看程序2-1,雖然這段代碼錶示出了Dog類,但是有一個很大的問題:創建出來的Dog對象的狀態很容易被改變。

比如我們可以直接修改程序2-2中的對象的狀態:

dog.name = "哮地犬";
dog.legs = 3;

你的狗的名字被不懷好意的人給改了,腿也少了一條!這種事情是危險、可怕的!

我們希望別人能夠“知道”自己的狗叫什麼名字、有幾條腿等信息,但是又要防止不懷好意的人隨便“傷害”自己的狗,怎麼辦呢?答案是封裝!

將對象的狀態和行為封裝起來,使用該對象的用戶只能通過對象本身提供的方法來訪問該對象的狀態。前面也說過,對象的當前狀態可能會改變,但是這種改變不是對象自發的,必須通過調用對象本身提供的方法來改變。如果不通過調用方法就能改變對象狀態,只能說明封裝性被破壞了。

換句話說,我們將對象的屬性(狀態)對外隱藏起來,這些狀態能否被訪問或修改,由對象自己來決定,決定的方式就是「給對象的使用者提供可調用的方法,用戶通過這些方法來進行訪問和修改」。

程序2-1可以改進為:

程序3-1
/**
 * 封裝后的狗類
 * @author Xing Xiaoguan
 */

public class Dog {
    private String name;
    private int age;
    private int legs;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getLegs() {
        return legs;
    }

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

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

    //行為——吠叫
    public void say() {
        System.out.println("我是" + name + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }

}

乍一看多了許多代碼,其實就多了兩個部分:

  1. 使用private修飾符來修飾成員變量,將其私有化,確保只能在本類內被訪問到,實現隱藏。
  2. 給成員變量提供了對應的訪問方法(getter方法)和修改方法(setter方法)。

其中我們給用戶能夠訪問或修改的成員變量都編寫上對應的setter或getter方法,如name。用戶不能訪問或修改的成員變量不寫setter或getter方法即可。

/**
 * 實例化一隻小狗,並和它玩
 */
public class Play {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("哮天犬");;
        dog.setAge(2);
        dog.say();
        dog.watchDoor();
    }
}

現在,用戶不能直接訪問或修改對象的狀態,必須通過提供的方法。對象並沒有給legs變量提供setter方法,這樣用戶就只能訪問狗有幾條腿,但是不能修改。狗腿被人“偷”了的事情也不會再發生了。

而且,當我們使用setter方法修改成員變量時,可以進行其他的操作,如錯誤檢查。比如設置age變量時:

//狗的平均壽命為10~15年,太大了不合理
public void setName(String name) {
    if (name > 0 && name < 30)
    	this.name = name;
}

現在,Dog類就比較安全了。

由上面的代碼可以看出,如果要訪問或修改成員變量,需要:

  • 成員變量是私有的
  • 一個公有的getter方法
  • 一個公有的setter方法

封裝還有一個優點就是:對外隱藏了具體實現,這樣的好處就是:我們可以修改內部實現,除了修改了該類的方法外,不會影響其他代碼。

封裝使對象對外變成了一個“黑箱”,用戶只會使用,但不清楚內部情況。

前面買水的例子也體現了封裝思想:行小觀買水的方式有很多,走路去、騎車去、甚至找比人幫忙,但是他改變買水的方式並不會對我造成影響。

3.2. 繼承

生活中除了狗,還有許多其他動物,比如貓、兔子……

程序3-2
/**
 * 貓類
 * @author Xing Xiaoguan
 */

public class Cat {
    private String name;
    private int age;
    private int legs;
    private String owner;//主人

    //getters and setters……

    //行為——叫
    public void say() {
        System.out.println("我是" + name + "喵喵喵");
    }

    //行為——捉老鼠
    public void catchMouse() {
        System.out.println("捉到一隻老鼠");
    }
}
程序2-5
/**
 * 兔子類
 * @author Xing Xiaoguan
 */

public class Rabbit {
    private String name;
    private int age;
    private int legs;
    private String home;//住址

	//getters and setters……

    //行為——叫
    public void say() {
        System.out.println("我是" + name + "咕咕咕");
    }

    //行為——搗葯
    public void makeMedicine() {
        System.out.println("在" + home + "搗葯");
    }
}

寫完這兩個類,發現有許多屬性和方法是重複的,如果需要再寫100個動物的類,那得

這些動物形態各異,但是它們都被統稱為“動物”,也就是說,我們仍能在它們身上找出相同的特點,比如它們都有名字、年齡、腿、能發出聲音……

前面介紹類的時候已經說了,類是對一類具有共同特徵的事物的抽象,所以此時我們還能從狗、貓、兔子這些類中再抽象出一個類——動物類。

程序3-3
/**
 * 動物類
 * @author Xing Xiaoguan
 */

public class Animal {
    private String name;
    private Integer age;
    private Integer legs;

    public void say() {
        System.out.println("我是"+name+"發出聲響");
    }
    //setters and getters……
}

這個更抽象的類就是父類,而狗、貓、兔子類是子類。子類可以使用extends關鍵字繼承父類的屬性和方法,這意味着相同的代碼只需要寫一遍。

程序3-4
/**
 * 狗類繼承父類
 * @author Xing Xiaoguan
 */

public class Dog extends Animal{

    //行為——吠叫
    public void say() {
        System.out.println("我是" + getName() + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }
}
程序3-5
/**
 * 貓類繼承父類
 * @author Xing Xiaoguan
 */

public class Cat extends Animal {
    private String owner;

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    //行為——喵喵叫
    public void say() {
        System.out.println("我是" + getName() + "喵喵喵");
    }

    //行為——捉老鼠
    public void catchMouse() {
        System.out.println("捉到一隻老鼠");
    }
}
程序3-6
/**
 * 兔子類繼承父類
 * @author Xing Xiaoguan
 */

public class Rabbit extends Animal {

    private String home;

    public String getHome() {
        return home;
    }

    public void setHome(String home) {
        this.home = home;
    }

    //行為——叫
    public void say() {
        System.out.println("我是" + getName() + "咕咕咕");
    }

    //行為——搗葯
    public void makeMedicine() {
        System.out.println("在" + home + "搗葯");
    }
}

觀察上面的子類和父類,可以發現:

  1. 子類不用再重複父類中已有的屬性和方法,能通過繼承獲取到。
  2. 子類比父類的功能更加豐富,子類可以擁有自己的屬性和方法
  3. 子類如果感覺繼承自父類的方法不合適,可以重寫父類的方法的實現過程,注意返回值和形參不能改變。

使用繼承的好處:

  • 提高代碼的復用性,不用再寫那麼多重複代碼了。
  • 使代碼便於維護,當我們需要修改某個公用方法時,不需要一個個類去修改,只需修改父類的該方法即可。

3.3. 多態

看下面一段代碼,我們來直觀體驗什麼是多態。

程序3-7
public static void main(String[] args) {
    Animal dog = new Dog();
    dog.setName("哮天犬");
    dog.say(dog.getName());
    
    Animal cat = new Cat();
    cat.setName("加菲貓");
    cat.say();

    Animal rabbit = new Rabbit();
    rabbit.setName("玉兔");
    rabbit.say();
}

運行,輸出:
我是哮天犬汪汪汪
我是加菲貓喵喵喵
我是玉兔咕咕咕
  1. Dog、Cat和Rabbit都是繼承了Animal父類。
  2. Dog、Cat和Rabbit都重寫了Animal的say(String name)方法。
  3. 在創建實例對象時,我們使用父類引用指向子類的對象。

使用多態應當注意一下幾點:Animal dog = new Dog()

  • 在多態中,子類對象只能調用父類中定義的方法,不能調用子類中獨有的方法。

    比如dog不能調用watchDoor()方法。

  • 在多態中,子類可以調用父類的所有方法。

  • 在多態中,子類如果重寫了父類的方法,那麼子類調用該方法時,調用的是子類重寫的方法。

在上面的代碼中,狗、貓、兔子對象都運行了say方法,但是輸出不同。

由此看出,不同的對象的同一行為具有不同的表現形式,這就是多態。

在實際的本例中,我們可以理解為:動物Animal,他們都會叫出聲。如果是狗,則叫的是汪汪汪;如果是貓,則叫的是喵喵喵;如果是兔子,則叫的是咕咕咕。

4. 總結

面向對象思想使我們在編程更加貼近現實世界,類是現實世界的抽象,對象則是一個個具體的事物。

封裝使一個個具體的事物更加獨立,繼承則使一些類似的事物之間具有聯繫,而多態則使事物的行為更加靈活多樣。

面向對象編程提高了軟件的重用性、靈活性、擴展性。

如有錯誤,還請指正

參考資料:

  • The Java Tutorials

  • 維基百科

  • 百度百科

  • Java核心技術 卷1

文章首發於公眾號「行人觀學」

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

【其他文章推薦】

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

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

Spring Redis開啟事務支持錯誤用法導致服務不可用

 

 

1.事故背景

在APP訪問服務器接口時需要從redis中獲取token進行校驗,服務器上線后發現一開始可以正常訪問,但只要短時間內請求量增長服務則無法響應

2.排查流程

(1)使用top指令查看CPU資源佔用還遠遠達不到瓶頸,排查因為CPU資源不足導致服務不可用的可能

(2)查看tomcat線程池配置,默認最大線程數為200,理論上可以支持目前服務器的訪問量

(3)使用jmap指令保存堆棧信息,jmap -dump:format=b,file=dump.log pid,pid為進程號

(4)使用Java visualVM打開保存的堆棧日誌dump.log,發現絕大部分的線程都阻塞在從redis連接池中獲取連接的代碼,如下圖所示

 3.原理分析

 根據堆棧日誌所显示得知線程訪問redis前需要從連接池隊列中推出一個連接,當連接池沒有連接時,則會阻塞等待,阻塞等待的時間可以自行設置MAA_WAIT參數,默認是-1表示不限時等待,目前項目使用默認配置,所以所有的線程都會一直阻塞在獲取連接的步驟,如果設置了最大等待時間,當超過最大等待時間會報出Could not get a resource from the pool的異常

(1)在spring配置文件中的stringRedisTemplate對象配置參數中打開了事務支持,而redis的事務支持是用MUTI和EXEC指令來支持,以下事務實例截圖來自菜鳥教程 https://www.runoob.com/redis/redis-transactions.html

 

(2)如果要保證在事務能正常執行,那麼在一個方法中多次操作redis必須是同一條連接,這樣才能保證事務能正常執行,所以在stringRedisTemplate會將連接綁定在當前線程,當第二次訪問redis時直接從當前線程中獲取連接,綁定連接源碼如下:

 

 

 (3)按照流程,先綁定連接,最後在finally代碼塊中釋放連接,看起來並沒有問題,但跳進去releaseConnection方法的代碼發現連接需要在事務提交后才能釋放,也就是說service方法上必須使用@Transation註解修飾,但因為業務方法上少寫了@Transation註解導致連接將一直綁定第一次獲取他的線程上,當線程池的線程被獲取完之後,其他線程就會就如阻塞等待狀態,導致服務不可用

 

 (4)如果加上@Transation註解,那麼方法執行完之後將會執行TransactionSynchronizationUtils.invokeAfterCompletion這個方法,mysql事務也是在這個方法執行commit操作,如下圖所示方法的第一個參數是List<TransactionSynchronization> synchronizations,代表可以有多個事務,redis,mysql等,都會此進行事務提交操作,這裏使用多態,根據對象的具體類型執行不同的方法,redis則執行redis的事務提交操作,mysql則執行mysql的事務提交操作

(5)以下為redis事務提交的代碼,也跟我們上面提到的一樣,發送exec指令提交事務

 

 4.如何修改代碼

(1)確認實際需求是否需要事務支持,如果需要則在對應方法上加上@Transaction註解

(2)如果不需要事務支持則將enableTransactionSupport設置為false

 

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

【其他文章推薦】

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

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

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

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

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

webpack的入門實踐,看這篇就夠了

webpack的入門實踐

我會將所有的讀者概括為初學者,即使你可能有基礎,學習本節之前我希望你具有一定的JavaScript和node基礎

  • 文中的 ... ...代表省略掉部分代碼,和上面的代碼相同
  • 文中的文件夾如果沒有說創建,並且項目默認沒有的是需要你手動增加的
  • 不會特別細緻,但是足夠入門

資源

  • 視頻教程
  • 我的個人博客

什麼是webpack

Web瀏覽器使用HTML,CSS和JavaScript。隨着項目的發展,跟蹤和配置所有這些文件變得非常複雜,解決這個問題就需要一個新的工具

類似webpack的工具還有Grunt和Gulp,webpack是模塊管理工具,把你的項目按照你的想法進行劃分模塊打包,舉個最簡單的例子,這個頁面需要加載一個 a.js和b.js,但是你只想加載一個js文件,就可以使用webpack將兩個文件進行合併,當然webpack的功能不止於此,代碼轉化、項目優化、代碼分割、代碼預編譯、自動構建、自動刷新…

再比如你想你的代碼兼容其他老的瀏覽器,你的css代碼兼容不同的瀏覽器內核,或者你想自動精簡掉你寫了但是沒有用到的代碼,這些都可以使用webpack實現

如果你是vue或者react等框架的使用者,肯定使用過 vue-cli 或 react-create-app 這類腳手架工具,那麼實現這個效果,就要學習webpack

快速入門

注意本文都是webpack4的內容

安裝

創建一個 webpackdemo文件夾,使用命令npm init -y快速初始化一個項目

安裝 webpack可以使用全局安裝

npm install webpack -g

但是我更推薦你在每個項目裏面單獨引入,這樣可以控製版本,如果你使用 webpack 4+ 版本,你還需要安裝 CLI。

npm install -D webpack@<version>
npm install -D webpack-cli

本文默認使用項目引入的方式,我們在根目錄下新建 src/index.js,webpack在不進行任何配置的情況下,會默認尋找這個文件

然後命令行執行node_modules\.bin\webpack,如果你是全局安裝的可以直接使用webpack命令

注意此時命令行爆黃色警告,這是沒有指定當前模式的原因,並且可以發現,目錄下多了一個 dist/main.js文件,這便是默認的輸出文件

為了體驗項目的打包,我們新建一個src/clg.js文件

export default function clg(msg) {
    console.log(msg);
}

我們在index.js裏面導入並使用

import clg from './clg';
clg('webpack init');

然後根目錄我們新建一個 index.html文件,引入打包后的文件

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./dist/main.js"></script>
</head>
<body>
</body>
</html>

然後修改一下打包命令,指定當前為開發模式,再次運行.\node_modules\.bin\webpack --mode development

這次打包沒有爆警告,並且我們打開index.html控制台查看結果

webpack.config.js配置文件

在前面,我們都是使用webpack-cli為我們提供的默認配置,如果我們想使用webpack更強大的功能還是需要自定義配置文件的,在根目錄新建webpack.config.js,執行webpack命令的時候會自動找到它

const path = require('path');

module.exports = {
    // 環境
    mode: 'development',
    // 目標文件
    entry: [path.resolve(__dirname, './src/index.js')],
    // 自定義輸出文件
    output: {
        path: path.resolve(__dirname, './dist'), //路徑
        filename: 'main.js' //文件名稱
    },
    // 插件
    plugins: [

    ],
    // 給導入的文件制定規則
    module: {

    }
}

為了方便調試,我們在 package.json中添加命令,此時執行命令npm run dev或npm run build就非常方便了

注意 scripts命令裏面可省略 .\node_modules\.bin\使用 npx webpack也是這個效果

 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "dev":"webpack --mode development",
   "build":"webpack --mode production"
 },

多個目標和輸出文件

在上面是一個目標文件index.js和一個輸出文件main.js,如果我們想要對多個目標文件進行打包,且輸出多個文件該怎麼辦呢?我們在根目錄新建一個 other.js

首先我們將entry修改為多個目標文件,並設置一個鍵名,然後修改輸出文件的名稱為變量[name]

const path = require('path');
module.exports = {
    // 環境
   mode: 'development',
    // 目標文件
   entry: {
        index: path.resolve(__dirname, './src/index.js'),
        other: path.resolve(__dirname, './src/other.js')
    },
    // [path.resolve(__dirname, './src/index.js'), path.resolve(__dirname, './src/other.js')],
    // 自定義輸出文件
    output: {
        path: path.resolve(__dirname, './dist'), //路徑
        filename: '[name].bundle.js' //文件名稱
    },
}

此時我們執行 npm run build可發現dist目錄的多個js文件

使用插件來測試程序

在上面,我們自己創建了一個 index.html文件來測試我們打包的文件是否正常,其實webpack為我們提供了更為自動的方式,在這裏我們將使用第一個webpcak插件html-webpack-plugin,首先需要安裝它

npm i html-webpack-plugin -D

然後我們在 webpack.config.js中配置使用這個插件

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    // 目標文件
    entry: {
        index: path.resolve(__dirname, './src/index.js'),
        other: path.resolve(__dirname, './src/other.js'),
    },
    // 自定義輸出文件
    output: {
        path: path.resolve(__dirname, './dist'), //路徑
        filename: 'main.js' //文件名稱
    },
    // 插件
    plugins: [
        new HtmlWebpackPlugin({
            title: "Webpack init",
        }),
    ],
}

此時,我們刪除 index.html文件,然後再次執行npm run build可以發現,webpack自動為我們創建了一個indedx.html文件,並引入了打包后的js文件

多個頁面

在上面,我們都是使用的一個 index.html單頁面,實際開發中漸進式的單頁面程序也比較多,但是還是會有多頁面的場景

簡單的修改一下webpack.config.js,多次實例化插件就可以了,filename為輸出文件名,chunks為這個頁面需要使用的js文件,當然如果你不是使用的自動生成頁面,可以使用template屬性指定你的頁面位置

module.exports = {
    ... ... 
    // 插件
    plugins: [
        new HtmlWebpackPlugin({
            filename:"index.html",
            title: "Webpack init",
            chunks:['index']
        }),
        new HtmlWebpackPlugin({
            filename:"other.html",
            title: "Webpack init",
            chunks:['other']
        }),
        new webpack.NamedModulesPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new CleanWebpackPlugin(),
    ],
    ... ...
}

此時我們使用npm run dev,此時可以發現dist目錄輸出了兩個頁面並引入不同的js文件

source map

打包后的js文件都混淆到了一個或者多個文件中,丟失了原本的文件格式,如果在運行過程中出現bug,很難定位原本的錯誤位置 source map 就可以解決這個問題

為了更容易地追蹤錯誤和警告,JavaScript 提供了 source map 功能,將編譯后的代碼映射回原始源代碼。如果一個錯誤來自於 b.js,source map 就會明確的告訴你。

開啟 source map 非常簡單,只需要在配置文件webpack.config.js中增加

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
	... ...
    devtool: "cheap-module-eval-source-map",
 	... ...
}

為了驗證是否生效,我們在other.js中增加

console.log('other');
console.error("error")

然後使用npm run dev,接着在控制台查看錯誤並點擊,便能跳轉到出錯的位置

devtool有多個模式,不同的性能和品質,開發環境中我們希望性能更好,生產環境我們希望質量更好詳細配置

開發環境可以使用cheap-module-eval-source-map、eval 、eval-source-map

生產環境可以使用inline-source-map、inline-cheap-module-source-map、cheap-source-map

觀察模式和webpack-dev-server

在上面的內容中,每次修改內容后都需要手動執行構建命令,webpack為我們提供了更為自動的方法

觀察模式

我們只需簡單的修改一下命令npm run dev --watch,同樣的我們為了方便,可以直接將命令寫入 package.json中

 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "dev": "webpack --mode development",
   "build": "webpack --mode production",
   "watch":"webpack --mode production --watch"
 },

此時,我們執行npm run watch命令后,只要修改文件中的內容,webpack即可自動構建

webpack-dev-server

但是在實際開發中,使用webpack-dev-server(簡稱wds)更為方便,它為我們提供了一個簡單的服務器,並且瀏覽器能夠實時加載,也就是說,當你修改文件保存后,瀏覽器可自動加載最新的內容,並且這一切都是發生在內存中,構建速度更快

安裝

npm i webpack-dev-server -D

同樣的我們在 package.json中添加一個命令

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "webpack --mode development",
  "build": "webpack --mode production",
  "watch": "webpack --mode production --watch",
  "server": "webpack-dev-server --mode development"
},

此時,我們只需執行npm run server,webpack便可自動創建一個服務器,並將項目運行在其中,當我們修改文件中的任意內容的時候,頁面便會自動刷新

如果使用過 vscode的插件live server的同學,不難發現,這就是類似的功能

我們還可以在 webpack.config.js中進一步的對 wds進行配置

module.exports = {
	... ...
    devServer: {
     /**
      * 日誌模式  friendly-errors-webpack-plugin 插件可以優化輸出
      * errors-only  只在發生錯誤時觸發
      * minimal 只在發生錯誤或者有新的編譯時輸出
      * none 沒有輸出
      * normal 標準輸出
      * verbose 全部輸出
      */
     stats: "errors-only",
     //默認地址 localhost
     host: process.env.HOST,
     //默認端口 8080
     port: process.env.PORT,
     //是否直接打開瀏覽器
     open: true,
 },
   ... ...
}

此時我們再次運行 npm run server,webpack便能按照我們的配置來構建了

HMR

模塊熱替換(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允許在運行時更新各種模塊,而無需進行完全刷新。

在上面的內容中,我們修改文件的部分內容,webpack都需要將項目重新構建並通知瀏覽器重新渲染,這個過程十分浪費資源,使用 HMR就可以實現,修改哪裡,重新加載哪裡的這個效果

NamedModulesPlugin插件是在熱加載時直接返回更新文件名

使用 HMR我們只需要簡單的配置即可

webpack.config.js

... ... 
const webpack = require('webpack');
module.exports = {
	... ...
    // 插件
    plugins: [
      	... ...
        new webpack.NamedModulesPlugin(),
        new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
		... ...
        //是否開啟熱更替
        hot: true
    },
}

為了驗證是否是局部更替,我么修改一下文件內容

index.js

import clg from './clg';
console.log('webpack init');

// module.hot Webpack通過全局變量公開HMR接口
if (module.hot) {
    module.hot.accept('./clg.js', function () {
        clg('檢測到clg模塊修改');
    })
}

此時我們使用 npm run server 將項目運行起來,簡單的修改 index.js文件中內容,發現控制台只打印了

我們再次修改clg.js中內容,打個空格再保存即可,此時驗證了我們想要的效果

生產環境和開發環境分離

開發環境(development)和生產環境(production)的構建目標差異很大,官方建議為每個環境編寫彼此獨立的 webpack 配置。

我們將新建兩個配置文件webpack.dev.js(開發環境)和webpack.prod.js(生產環境)但是它們具有很多相同的配置,所以我們再新建一個webpack.common.js(通用配置)文件

我們使用webpack-merge插件來將不同的環境配置文件和通用配置文件進行合併,並且使用clean-webpack-plugin插件來每次重置我們的構建文件夾

npm i webpack-merge -D
npm i clean-webpack-plugin -D

webpack.common.js

const path = require('path');
const webpack = require('webpack');
const {
    CleanWebpackPlugin
} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: [path.resolve(__dirname, './src/index.js')],
    plugins: [
        new CleanWebpackPlugin(),
        new webpack.NamedModulesPlugin(),
        new HtmlWebpackPlugin({
            title: 'Webpack init'
        })
    ],
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            
        ]
    }
};

webpack.dev.js

我們再開發環境配置文件中配置 server的相關信息,並且打開source-map

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode:"development",
    devtool: 'eval',
    devServer: {
        stats: "errors-only",
        //默認地址 localhost
        host: process.env.HOST,
        //默認端口 8080
        port: process.env.PORT,
        //是否直接打開瀏覽器
        open: true,
        //是否開啟熱更替
        hot: true,
    },
    module: {
        rules: [
            //打包css文件的規則
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            }
        ]
    }
});

webpack.prod.js

我們在生產環境配置文件中省略掉其他配置文件

const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: "production",
    
});

然後我們在 package.json增加一些命令

"envdev":"webpack-dev-server --config webpack.dev.js",
"envbuild":"webpack --config webpack.prod.js"

此時,我們的項目構建就更加清晰了

管理資源文件

webpack不僅僅是打包 js文件這麼簡單,此處我們簡單的介紹幾個常用的資源打包方式,更詳細的內容可以參考官方文檔

管理css

使用過腳手架的同學應該都記得,項目裏面的css文件可以通過js直接引入的方式使用

import 'xxx.css';

來簡單實踐一下,首先安裝插件

npm i  style-loader css-loader -D

為了展示我們的打包效果,我們新建一個 js/divdoc.js文件用來在頁面中渲染出一個字符串(此時我們已將clg.js也轉移到 ./js 文件夾)

export default function divdoc() {
    //創建一個dom
    let element = document.createElement('div');
    element.innerHTML = "webpack init";
    element.classList.add('init');
    //將dom渲染到頁面上
    document.body.appendChild(element);
}

在index.js中導入並使用

import clg from './js/clg';
import divdoc from './js/divdoc';
console.log('webpack init now');

divdoc();

// module.hot Webpack通過全局變量公開HMR接口
if (module.hot) {
    module.hot.accept('./js/clg.js', function () {
        clg('檢測到clg模塊修改');
    })
}

此時運行 npm run server查看效果

接下來我們新建 src/css/app.css文件

我們前面渲染的dom節點是包含一個 class名為 init的

.init{
    color: red;
}

編寫規則webpack.dev.js

module.exports = {
	... ...
    // 給導入的文件制定規則
    module: {
        rules: [
            //打包css文件的規則
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
        ]
    }
}

在index.js中導入,並查看效果

import  './css/app.css';

順便可以打包一下less,首先安裝插件npm i -D less-loader,然後寫一下規則

module: {
    rules: [
        //打包css文件的規則
        {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        },
        {
            test: /\.less$/,
            use: ['style-loader', 'css-loader', 'less-loader']
        },
    ]
}

我們新建一個 src/css/app.less文件

.init{
    color: red;
}

我們在 index.js中註釋掉原本導入的 app.css,然後導入less文件

import './css/app.less';

重新構建項目,查看項目,效果依然生效,同理sass、stylus也是這個用法,這裏不再贅述

分離css

在前面,我們打包css,最終都是將css代碼添加到頁面的 style標籤中,如果我們想將所有的css都打包到專門的文件裏面可以使用mini-css-extract-plugin插件

npm i mini-css-extract-plugin -D

然後修改一下配置webpack.prod.js

... ... 
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  	... ...
    // 插件
    plugins: [
        new MiniCssExtractPlugin({
            filename: "styles/[name].css",
        })
    ],
    // 給導入的文件制定規則
    module: {
        rules: [
            //打包css文件的規則
            // {
            //     test: /\.css$/,
            //     use: ['style-loader', 'css-loader']
            // },
            {
                test: /\.css$/i,
                use: [
                    MiniCssExtractPlugin.loader, 'css-loader'
                ],
            }
        ]
    }
    ... ...
}

此時我們執行npm run build可以發現dist目錄下面創建了一個index.css文件,因為我們是在 index.js中導入css文件的,[name]的值是js的文件名,而不是css的名

當然如果你想指定輸入和導出的css的名字也是可以的,使用這種方式,你就不需要在js中再次引入css文件了

entry: {
    index: path.resolve(__dirname, './src/index.js'),
    other: path.resolve(__dirname, './src/other.js'),
    app: path.resolve(__dirname, './src/css/app.css')
},

webpack在處理樣式方面還有很多很強大的插件,比如purgecss可以精簡掉頁面中沒有使用的css樣式、Autoprefixer可以自動給你添加不同瀏覽器兼容的css插件

管理圖片和字體

在網頁中,圖片一般都是加載的網路路徑,但是在開發中我們都是使用的本地圖片,那麼為了保證上線后和本地的資源位置保持一致,我們可以使用webpack來進行一下打包,最後統一上傳oss存儲

首先需要安裝url-loader

npm i url-loader -D

然後我們在 webpack.common.js中進行配置

const path = require('path');
const webpack = require('webpack');

const {
    CleanWebpackPlugin
} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: [path.join(__dirname, './src/index.js')],
    plugins: [
        new CleanWebpackPlugin(),
        new webpack.NamedModulesPlugin(),
        new HtmlWebpackPlugin({
            title: 'Webpack init'
        })
    ],
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
        //靜態文件打包的網絡路徑
        publicPath:'https://www.lookroot.cn/assets/'
    },
    module: {
        rules: [
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                        //超過這個大小,圖片就打包為圖片,不超過就打包為base64格式的代碼
                        limit: 1000,
                        //打包文件名
                        name: "img/[hash].[ext]",
                    },
                }
            },
        ]
    }
};

為了驗證,首先我們放一張圖片logo.jpg到src/asserts/img目錄下,我們簡單的修改一下app.css

.init {
  color: red;

}
body {
  background-image: url("../assets/img/logo.jpg");
  background-repeat: no-repeat;
}

同樣的我們也可以在js代碼里使用圖片,修改一下divdoc.js

import logo from "../assets/img/logo.jpg";
export default function divdoc() {
	... ...
    // 插入一張圖片
    let img = document.createElement('img');
    img.src = logo;
    element.appendChild(img);
   ... ...
}

然後使用npm run envdev運行起來,效果是正常的

然後我們使用npm run envbuild執行編譯,然後我們打開dist/img/main.css

body {
  background-image: url(https://www.lookroot.cn/assets/img/494654d849ba012e2aab0505d7c82dc0.jpg);
  background-repeat: no-repeat;
}

我們可以發現,webpack就給我們自動加上了網絡路徑,對於圖片的處理,還有可以優化圖片的[image-webpack-loader(https://github.com/tcoopman/image-webpack-loader)、可以自動生成雪碧圖的postcss-sprites

除了上面我說的這些資源外,webpack還支持非常多的資源格式,只要理解這個思想,使用也不難

代碼檢查和代碼轉換

Eslint代碼檢查

eslint是實際開發中非常常用的代碼檢查工具,我們在webpack中使用它來進行代碼檢查

首先安裝eslint、loader、錯誤格式化插件

npm i eslint  eslint-loader eslint-friendly-formatter -D

然後我們在根目錄新建一個.eslintrc.json,當然你也可以使用命令npx eslint --init來初始化配置文件

rules代表規則,這裏我設置一個禁止使用 alert代碼來測試是否可以完成代碼檢查,更多規則請看文檔

{
    "env": {
        "browser": true,
        "es6": true,
        "node": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "ecmaVersion": 11,
        "sourceType": "module"
    },
    "rules": {
        "no-alert": 2
    }
}

然後在webpack.dev.js中增加配置

 module: {
     rules: [
        ... ... 
         {
             test: /\.js$/,
             loader: 'eslint-loader',
             enforce: 'pre',
             include: [path.resolve(__dirname, 'src')],
             options: {
                 formatter: require('eslint-friendly-formatter')
             }
         }
     ]
 }

然後我們在 index.js文件中增加一句alert("webpack init"),然後使用命令npm run envdev發現報錯,eslint成功捕捉到了錯誤

同樣的你還可以使用 StyleLint工具來檢查你的css代碼

babel代碼轉化

在實際開發中如果使用了 es6+的代碼,有些瀏覽器是不支持的,為了兼容,所有需要將代碼進一步轉化,可以使用babel進行轉化

babel的使用稍微比較繁瑣,本文只介紹在webpack的使用方法,更多細緻的東西請自行查閱

安裝本體和loader babel-loader @babel/core, @babel/preset-env是轉換插件的預設組合,@babel/plugin-transform-runtime用來解決一些瀏覽器不支持的方法和對象問題

npm i @babel/runtime -S
npm i  babel-loader  @babel/core @babel/preset-env @babel/plugin-transform-runtime -D

然後我們在根目錄新建一個配置文件.babelrc,使用@babel/preset-env提供的插件集合能完成大部分的工作了,targets表示我們的代碼要運行到哪些平台上面,更為詳細的請點擊

{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": 3,
                "targets": {
                  	"browsers": [ "ie >= 8", "chrome >= 62" ]
                }
            }
        ]
    ]
}

然後修改一下webpack.dev.js

module: {
    rules: [
       ... ... 
        {
            test: /\.js$/,
            use: [{
                loader: 'babel-loader',
            }]
        }
    ]
}

為了驗證代碼是否轉換成功,我們在index.js中添加代碼

const say=(msg)=>{
    console.log(msg);
}

然後使用命令npm run envdev,並打開source map 查看源文件,可以發現箭頭函數已經被轉換了

本節的內容就是這些,下一次將會有個簡單的實戰,對於webpack還有很多要學習的地方,比如打包優化、插件編寫等等學完基礎以後,這些就需要你自己去探索

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

【其他文章推薦】

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

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

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

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

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

接口調用超時的實現原理

  平常開發過程中,如果涉及到RPC調用,對於服務調用方和服務提供方,都是可以設置接口超時時間的。以調用方為例,調用方需要調用遠程的一個接口,為了保證服務的質量,一般會設置調用接口的超時時間,比如將調用接口的超時時間設置為1秒,當調用遠程接口后,經過1秒還未拿到結果,那麼就認為是超時了,調用方就不會繼續等待服務提供方返回結果,而是直接拋出一個SocketTimeOutException。

  其實不僅僅是RPC接口調用需要設置超時時間,數據庫、緩存這些一樣的,一般都會設置超時時間,不能讓程序無休止的等待下去。

  那麼問題來了!!應用或者說框架,是如何設置超時時間的呢?我們設置超時時間為1秒,那麼程序是怎麼保證1秒后就停止調用了呢?這也就是本文要介紹的,接口調用設置超時時間的原理。

  本文將以設置調用數據庫超時為例,介紹設置超時時間是怎麼實現的,明白這個原理后,其他比如RPC調用、緩存調用的超時原理也就明白。

 

CallUtils-帶有超時的執行工具類

  下面是一個CallUtils,包含一個線程池和execute方法,execute方法中默認超時時間為1秒。

package cn.ganlixin.util;
import java.util.concurrent.*;

/**
 * 帶有超時的任務執行器
 */
public class CallUtils {

    /**
     * 用於執行任務的線程池
     */
    private static ExecutorService executorService = Executors.newFixedThreadPool(5);

    /**
     * 執行任務,並且超時時間為1秒
     *
     * @param callable 需要執行的任務
     * @param <T>      任務執行完畢后返回值類型
     * @return 任務結果
     */
    public static <T> T execute(Callable<T> callable) {

        // 提交任務
        Future<T> future = executorService.submit(callable);

        try {
            return future.get(1, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }

        return null;
    }
}

  

UserService-將調用數據庫

  UserService使用CallUtils來執行數據庫操作,因為CallUtils設置有調用超時:

package cn.ganlixin.service;

import cn.ganlixin.dao.UserDAO;
import cn.ganlixin.model.User;
import cn.ganlixin.util.CallUtils;

import java.util.List;

public class UserService {

    private UserDAO userDAO = new UserDAO();

    public List<User> getAllUser() {
        return CallUtils.execute(() -> userDAO.getAllUserFromDB());
    }
}

  

UserDAO-耗時的數據庫操作

  userDAO中,作為測試,只提供了一個getAllUserFromDB方法,休眠2秒來模擬數據庫操作,因為UserService使用CallUtils來調用該方法,CallUtils的超時時間為1秒,所以該數據庫操作必定會超時:

package cn.ganlixin.dao;
import cn.ganlixin.model.User;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class UserDAO {

    /**
     * 從DB獲取全量user列表
     */
    public List<User> getAllUserFromDB() {
        try {
            // 模擬數據庫操作,耗時2秒
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }
}

  

進行測試

  創建測試類,測試UserService的getAllUser方法,因為getAllUser方法中使用CallUtils來調用數據庫操作,CallUtils的超時時間為1秒,而數據庫操作需要2秒,所以getAllUser方法必然會超時。

package cn.ganlixin.test;
import cn.ganlixin.service.UserService;
import org.junit.Test;

public class TestUserService {

    public UserService userService = new UserService();

    @Test
    public void testGetAllUser() {
        userService.getAllUser();
    }
}

  執行測試,輸出如下:

  

 

 

總結

  本文演示了接口超時調用的原理,實現接口調用超時,無非是通過將任務提交到線程池后,使用future.get,設置超時時間即可。

  上面的代碼很多細節都不太規範,比如涉及到數據庫的超時,應該是數據庫連接池的超時配置,而我在演示時是直接使用CallUtils來替代了,但是明白這個原理就OK。

 

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

※新北清潔公司,居家、辦公、裝潢細清專業服務

IDEA版本彩虹屁插件idea-rainbow-fart,一個在你編程時瘋狂稱讚你的 IDEA擴展插件

緣起

是否聽說過程序員鼓勵師,不久前出了一款vscode的插件rainbow-fart,可以在寫代碼的時候,匹配到特定關鍵詞就瘋狂的拍你馬屁。

vscode的下載嘗試過,但是作為日常將IDEA作為主力生產工具的同學來說,如何體驗呢? 於是假期花了一點時間,寫了一個idea版本的插件idea-rainbow-fart。

使用說明

默認使用中文語音包,可以在setting里設置

打開設置:

選擇第三方語音包:

可以到 https://github.com/topics/vscode-rainbow-fart 查找語音包。

點擊確定生效:

原理

沒啥原理,就是一款簡單的idea插件,對沒寫過插件的我來說,需要先看下官方文檔,基本上看下面這一篇就OK:

https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html

讀取語音包

先來看下語音包的設計:


{
  "name": "KugimiyaRie",
  "display-name": "KugimiyaRie 釘宮理惠 (Japanese)",
  "avatar": "louise.png",
  "avatar-dark": "shana.png",
  "version": "0.0.1",
  "description": "傲嬌釘宮,鞭寫鞭罵",
  "languages": [
    "javascript"
  ],
  "author": "zthxxx",
  "gender": "female",
  "locale": "jp",
  "contributes": [
    {
      "keywords": [
        "function",
        "=>"
      ],
      "voices": [
        "function_01.mp3",
        "function_02.mp3",
        "function_03.mp3"
      ]
    },
	...
	]
}

對Java來說,定義兩個bean類,解析json即可:

/**
     * 加載配置
     */
    public static void loadConfig() {
        try {
            //
            FartSettings settings = FartSettings.getInstance();
            if (!settings.isEnable()) {
                return;
            }
            String json = readVoicePackageJson("manifest.json");
            Gson gson = new Gson();
            Manifest manifest = gson.fromJson(json, Manifest.class);
            // load contributes.json
            if (manifest.getContributes() == null) {
                String contributesText = readVoicePackageJson("contributes.json");
                Manifest contributes = gson.fromJson(contributesText, Manifest.class);
                if (contributes.getContributes() != null) {
                    manifest.setContributes(contributes.getContributes());
                }
            }
            Context.init(manifest);

        } catch (IOException e) {
        }
    }

監控用戶輸入

自定義一個Handler類繼承TypedActionHandlerBase即可,需要實現的方法原型是:

public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext)

chartTyped就是輸入的字符,我們可以簡單粗暴的將這些組合到一起即可,用一個list緩存,然後將拼接后的字符串匹配關鍵詞。

     private List<String> candidates = new ArrayList<>();

    @Override
    public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext) {
        candidates.add(String.valueOf(charTyped));
        String str = StringUtils.join(candidates, "");
        try {
            List<String> voices = Context.getCandidate(str);
            if (!voices.isEmpty()) {
                Context.play(voices);
                candidates.clear();
            }
        }catch (Exception e){
            // TODO
            candidates.clear();
        }

        if (this.myOriginalHandler != null) {
            this.myOriginalHandler.execute(editor, charTyped, dataContext);
        }
    }

匹配關鍵詞更簡單,將讀取出來的json,放到hashmap中,然後遍歷map,如果包含關鍵詞就作為語音候選:

    public static List<String> getCandidate(String inputHistory) {


        final List<String> candidate = new ArrayList<>();

        FartSettings settings = FartSettings.getInstance();
        if (!settings.isEnable()) {
            return candidate;
        }
        if (keyword2Voices != null) {
            keyword2Voices.forEach((keyword, voices) -> {
                if (inputHistory.contains(keyword)) {
                    candidate.addAll(voices);
                }
            });
        }
        if (candidate.isEmpty()) {
            candidate.addAll(findSpecialKeyword(inputHistory));
        }
        return candidate;
    }

如果找到候選,就播放。

播放

為了防止同時播放多個語音,我們用一個單線程線程池來搞定。播放器使用javazoom.jl.player.Player

  /**
     * play in a single thread pool
     */
    static ExecutorService playerTheadPool;
	static {
        ThreadFactory playerFactory = new ThreadFactoryBuilder()
                .setNameFormat("player-pool-%d").build();
        playerTheadPool = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024), playerFactory, new ThreadPoolExecutor.AbortPolicy());
    }
	
	public static void play(List<String> voices) {

        FartSettings settings = FartSettings.getInstance();
        if (!settings.isEnable()) {
            return;
        }
        // play in single thread
        playerTheadPool.submit(() -> {
            String file = voices.get(new Random().nextInt() % voices.size());
            try {
                InputStream inputStream = null;
                if (StringUtils.isEmpty(settings.getCustomVoicePackage())) {
                    inputStream = Context.class.getResourceAsStream("/build-in-voice-chinese/" + file);
                } else {
                    File mp3File = Paths.get(settings.getCustomVoicePackage(), file).toFile();
                    if (mp3File.exists()) {
                        try {
                            inputStream = new FileInputStream(mp3File);
                        } catch (FileNotFoundException e) {
                        }
                    } else {
                        return;
                    }
                }
                if (inputStream != null) {
                    Player player = new Player(inputStream);
                    player.play();
                    player.close();
                }
            } catch (JavaLayerException e) {
            }
        });
    }

end

開源地址: https://github.com/jadepeng/idea-rainbow-fart

歡迎大家點贊!

作者:Jadepeng
出處:jqpeng的技術記事本–http://www.cnblogs.com/xiaoqi
您的支持是對博主最大的鼓勵,感謝您的認真閱讀。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

下半年這幾個顏色最火

現在正值換季,精緻的豬豬女孩必定又想買衣服來裝扮自己,除了要考慮款式,最重要的還是顏色的選擇!挑不挑皮?流不流行?

顏色沒選對,再貴的衣服你也穿不高級!

很多妹子第一想到就是黑白灰!但只知道這種搭配!那必定成不了人群中最亮眼的那一個!還怎麼美美的在冬季約男神雪吃火鍋了!

想要穿得比別人流行一步,那就趕緊小編的腳步,經過精挑細選,終於挑選了5種顏色出來,絕對百搭不挑皮!

奶油甜桃

光聽“奶油甜桃”這個名字都能感受到這個低飽和度的顏色是有多麼仙氣逼人!

可能朦朦朧朧的奶油甜桃色就像奶油里了溶進了幾滴蜜桃果汁,所以大家對它的第一印象都會是溫柔女孩子獨有。

大面積或者成套搭配,也是時髦感上線,氣場全開,slay全場!

青石藍

和其他顏色不同,青石藍總給人一種治癒的感覺,彷彿只要看着它世界都安靜下來了。

灰濛濛的朦朧感是這個顏色的最大特點,比以往的藍色要更加溫柔,更加高級!

青石藍在今年的秀場上可謂是大放光彩,這種低調不普通的美吸引着每一個人!

巧妙利用一些配飾做點綴,也是一個不錯的選擇!

無論是針織衫還是西裝外套,都是怎麼穿怎麼高級!大氣優雅的代名詞!

果鳩鳥粉

明亮艷麗的果鳩粉,飽和度極高,但它相比於熒光粉會更有氣場,在灰色度的秋冬增添一抹靚麗!

果鳩粉不僅單獨搭配時能撐起整個look,與同樣明亮的顏色搭配起來更是相得益彰!

而明亮艷麗的果鳩粉飽和度較高,搭配起最受喜愛的黑、白、灰也是氣場全開!毫無違和感呢!

這個無比撩人的顏色無論用在裙裝或者外套上面,都是在寂寥的秋季顯得格外吸睛!

石板路褐

石板路褐是一種堅果棉花糖巧克力混雜的冰凍甜品,隨之又可稱它為榛子巧克力色。

隸屬於深色系又不會像黑色那麼沉重,但又自帶的高級感,在各大秋冬秀場,它都是非常重要的一個角色!

同色系的穿搭,又高級又很顯質感!非常適合在寒冷的街頭,犹如電影畫面一般迷人!

不過因為這個顏色的厚重,在搭配時與溫柔的紫色相撞,復古又高級!總能演繹別樣風情!

伊甸園綠

充滿神話色彩的伊甸園綠看起來古樸又老舊,恍如密林深處的寶藏,隱隱發光,讓人忍不住想要窺探一番!

帶有一點童話色彩的神秘伊甸園,它與牛油果綠最大的不同就是要更加成熟,莊嚴,像隱藏在仙境中的綠寶石一樣撩人!

這個顏色給我最大的感覺就是復古,自帶一股上世紀的神秘氣息,穿上它彷佛置身於上個

無論是和高飽和度顏色還是灰調的顏色搭配,迸發出滿滿的時尚感,犹如一場舒適治癒的視覺體驗!

有時利用一些配飾做一件點綴也是給整體搭配加分不少!

時尚總是瞬息萬變的,不同的色綵帶着不同的視覺效果,願你找到屬於你的專屬色!

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※你應該要知道的電子煙懶人包!

※電子煙有爭議?真相解密

※全台最大電子煙交易平台?

九分褲 + 短靴,好看爆了

想要風度與溫度時刻並存?九分褲+短靴的最強cp組就是一秒時髦的絕對說明,不僅自帶瀟灑屬性,且出街必為凹造型利器

知道時髦的菇涼都不想放棄九分褲,冬天想要穿九分褲又想要護腳踝,一雙短靴就能幫到你。九分褲+短靴,絕對是冬天最時髦保暖的搭配,還能輕鬆穿出大長腿!

對時尚信息嗅覺靈敏的明星小花們當然不會錯過這個搭配,國內國外各大明星們都已經是這套造型的死忠粉!

看到了吧~時髦精們可都在這樣搭配,第一是可以顯腿長,第二是腿部曲線可以很利落,還可以保暖。(加秋褲)

九分微喇褲+短靴

九分微喇褲早已經悄無聲息地回歸,並迅速席捲整個時尚圈。它隨着時間推移演變成現在的優雅和時尚的單品。

想讓自己的腿看起來更修長,就試試黑色喇叭褲配黑色踝靴。踝靴盡量選擇貼合腳部和腳踝的款式。

除了經典的黑色,棕紅色也是很喜歡的顏色,會有一種很高級的復古感~

棕色也很百搭,會讓整體造型看起來更加優雅有氣質~也很符合秋冬的色調~

九分直筒褲+短靴

雖然小穿覺得九分微喇的褲子配短靴最時髦,但是街拍達人們還是九分直筒穿的人最多。在這裏強烈推薦高腰直筒褲,真的會讓你的身材比例變得非常好。

無論是酷帥的皮衣還是正式的西裝,或者是簡潔的大衣,有了直筒九分褲+短靴,一切都是那麼簡單。

這一身的時尚元素必須要用一雙非常帶有都市元素的鞋子相配才能顯得和諧,還避免了這種款型的褲子顯腿短的弊端~

可以拿顏色鮮艷的毛呢大衣出來啦~比如說羊羔毛外套

或者今年非常流行的超長大衣,時尚度UP!

九分小腳褲+短靴

小腳褲也很時髦,但是對腿型的要求比較高一些~比喇叭褲少了一些復古的感覺,多了街頭雅痞的味道~

小腳褲有個很好的地方在於,它可以很輕鬆地塞進靴子裏面。所以當你選了和靴子一樣顏色褲子,比如黑色。你的腿和腳都能連在一起,自然就增高了。

九分褲+短靴,時髦精們都這麼穿!你們就趕緊穿去吧,不美不要錢,時髦的擋都擋不住!

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※你應該要知道的電子煙懶人包!

※電子煙有爭議?真相解密

※全台最大電子煙交易平台?

徐璐攜張銘恩兩人同是格紋系穿搭,可養眼了

徐璐和張銘恩都是很優秀的青年演員,也是當下娛樂圈裡令人羡慕的一對情侶,更是在綜藝里談起了“甜甜的戀愛”,其實,說實話,徐璐和張銘恩還是蠻般配的,這不,看到徐璐攜張銘恩來機場,兩人同是格紋系穿搭,情侶這麼穿可養眼了!來看下怎麼樣的吧。

先看徐璐,從她的穿着上明顯能夠感受到天氣冷了很多,只見需要戴一頂黑色的毛呢漁夫帽,還戴上了黑超,加上我們的徐璐臉比較小,因此,這樣的話就感覺擋住了整張臉,凸顯了臉的精緻感,還顯得頗為低調。

穿着上,徐璐外穿了一件格紋的呢大衣,穿上這件呢大衣的徐璐看着頗為大氣和有英倫風,不得不說,穿上呢大衣還是比較顯氣質的,在秋冬的時候,同學們一定要試試。

在搭配的時候,徐璐選擇下搭了一條格紋的休閑褲,上下都是格紋系列的穿搭,保持了風格上的一致性,然後又看到她往腳上搭配了一雙帆布鞋,整套造型就算是完成了,休閑但是挺好看的一身。

再看張銘恩,可以說是帥氣又時髦,只見張銘恩戴了頂牛仔系列的鴨舌帽,還戴上了金屬框的眼鏡,就是潮男必備的單品呢,穿着上,張銘恩這次上衣也是牛仔風的,來看下他是怎麼穿的吧。

看張銘恩首先是在身上穿了件淺藍色的牛仔衣,顯得清爽帥氣,再接着,又看他內搭了一件白色的T恤,背上一款帆布包的樣子像極了還在上大學的男生呢,這麼穿還挺顯校園風的。

選擇的褲子上,不知道是不是默契,還是刻意這麼穿的,張銘恩選擇穿了條格紋的休閑褲,也是黑白條紋風格的,和一胖的徐璐風格一致,這才是情侶該有的裝扮呢,穿成這樣出行,很拉風,也很養眼。

包括腳上穿的鞋子也是一樣的風格,看張銘恩腳上穿了一雙黃顏色的帆布鞋,而旁邊的徐璐穿了雙帆布鞋,我們知道,帆布鞋是很顯青春氣息的,因此,這麼穿的兩人就像是漫步在校園的情侶一般。

好了,徐璐和張銘恩的出行LOOK就是這樣,都是格紋系的穿搭,好看又經典,天氣已經涼下來了,要是兩口子出門不知道該怎麼穿,倒是可以參考下徐璐他們,那麼,你覺得徐璐和張銘恩的造型好看嗎?

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※你應該要知道的電子煙懶人包!

※電子煙有爭議?真相解密

※全台最大電子煙交易平台?

秋季絲綢半裙搭配推薦,穿出迷人吸睛造型

叮咚,秋天都過了這麼久了,你的秋季搭配可還滿意?是勇敢嘗試新的風格,找到全新的自己?還是走回老路,繼續在自己的世界里綻放美麗?無論如何,出發點都是為了能變美,愛美是人的天性,尤其是女人,在美的這件事情上永遠是積極的。為了能讓各位小仙女成為秋季的女神,今天,就來給各位介紹一個新的單品搭配,它就是絲綢半裙,絲綢半裙不會像絲綢連衣裙那樣有着太強烈的睡衣感,反而看着會比較利落,順滑的材質看起來就顯得很高級,只需加入基本單品,也能讓你分分鐘成為女神。

衛衣

不用多說都知道衛衣有多受歡迎了吧,男女老少幾乎人手一件,在經歷了潮流的變化后,它的風格更加的多樣化,款式也變得越來越多了,可搭配性更強了。如果你還停留在衛衣只能搭配休閑或運動類單品階段,那就得認真看下這篇搭配了。

▲衛衣一般還是給人以休閑為主,在搭配上難免以固有的印象去用同樣是休閑的單品去搭配。其實可以嘗試一下絲質的半裙,結合衛衣的休閑和半裙的優雅,兩者各自中和,達到一種均衡的搭配。

▲香檳色一直是溫柔、知性的體現,加上真絲材質的半裙,盡顯高貴的氣質。上衣搭配一件薄荷綠的衛衣,給人眼前一亮,是一種清新舒適的感覺,和柔和的香檳色真的是太搭了,誰說秋天就得是清一色的深色系,小清新的顏色也能成為秋季的一道美麗風景。

毛衣

▲毛衣也是不用多說大家都知道的一件熱門單品,從小穿到大,現在的毛衣花色和款式都很時髦,深受時尚博主的喜愛。一些毛衣看起來差不多,但它有很多細節的小設計做的相當不錯。比如,在袖子上加寬了袖口變成喇叭袖,或者是高領的毛衣,既顯氣質,又能保暖。在搭配半裙時,毛衣可以是短版的類型,塑造出大長腿的效果。下搭一件中長款的半裙配短靴,增加成熟與個性。或者是針織衫搭配半裙,以及一雙豹紋高跟鞋,溫婉大氣。

▲毛衣和真絲半裙的搭配就是非常的慵懶悠閑。如果是新手,就選擇保守點的搭配,可以選擇同色搭配,區分出顏色的深淺,穿出層次感。然後加一些飾品就能完成造型了。

各種外套

▲西裝外套現在搭配單品的範圍越來越廣了,用來搭配絲綢半裙美出天際了。絲綢半裙是溫柔且具有女人味的,而西裝外套是幹練與成熟的結合,這兩個一強一柔,打造一個獨立、知性的女性。

▲這種強與柔的結合還有皮衣的搭配,而用皮衣來搭配的話,又多了一份個性。

▲在與這些絲綢半裙的搭配中,還有很多外套都非常適合的,像襯衫、長款外套或者短版外套,這也證明了絲質的半裙是非常百搭的。

本站聲明:網站內容來源於http://www.shelive.net/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

※你應該要知道的電子煙懶人包!

※電子煙有爭議?真相解密

※全台最大電子煙交易平台?

萬達5.2億拍下淄博一地塊 重啟重資產模式?

萬達5.2億拍下淄博一地塊 重啟重資產模式?

東方財經網 2017-09-15 22:35:56 來源:

騰訊財經 作者 孫春芳

萬達商業又拿地了。

淄博(樓盤)市國土資源局網站显示,萬達商業以55.2億元底價拿下一地塊。

這一地塊中,住宅佔86191平米,商業佔48847平米。

該地塊的出讓條件規定:1・競買人(或其控股股東)須為2016年世界500強企業;2・競買人須為中國房地產業協會官方網站公布的2016中國商業地產企業品牌價值前十強企業

事實上,入圍2016年世界500強的國內房企只有綠地、萬達、萬科、恆大四家;而四家中符合“2016中國商業地產企業品牌價值前十強企業”的只有萬達一家。

按照王健林的戰略,萬達將逐步去地產化,最終不再搞自己拿地的重資產項目。

王健林在2016年萬達年會上表示:2017年新開業50個萬達廣場,其中11個重資產項目,39個是輕資產項目,以後每年重資產項目要減到5個左右。2020年以後原則上不再搞重資產,全部為輕資產。除了萬達城配套的住宅外,萬達原則上也不再新增住宅開發。

今年7月,萬達火速將13個文旅城賣給融創,按照萬達官方的解釋:由於目前房地產公司在A股的IPO發行幾乎處於停滯狀態,可能影響到萬達商業的上市步伐,而萬達轉型輕資產後,則可以不被划入“房地產”板塊,從而避開政策風險。

  (出處:騰訊財經 2017年09月15日)

  聲明:轉載上述內容屬於廣告或出於傳遞更多信息之目的,不代表東方財經網的觀點。文章內容僅供參考,不構成投資建議。投資者據此操作,風險自負。

【其他文章推薦】

※實木地板、海島型地板、耐磨地板怎麼挑? 木地板三倍價差的秘密!!

新屋購入,尋找台中室內設計師?是否可先免費估價丈量?

※挑好磚一點都不難!馬賽克磚挑選眉角小撇步!

※想知道北部最多平價、庫存出清的家具工廠推薦在哪裡?

※分享木質地板DIY自行施工教學影片

※推薦沙發修理,老師傅的專業手工!

※家中裝潢適合超耐磨木地板嗎?注意五大鋪設狀況