初探Google Guava


Guava地址:https://github.com/google/guava

第一次接觸我是在16年春github上,當時在找單機查緩存方法,google guava當初取名是因為JAVA的類庫不好用,所以谷歌工程師自己開發一套,想着google出品必屬精品理念,我們一起來了解一下。

guava在還沒做分布式處理上,單機單整合上大行其道。所以guava在程序性能優化上下了不少的工夫,我們稱其為單塊架構的利器

我認為強大的有幾點:1.集合處理   2.EventBus消息總線處理  3.guava cache 單機緩存處理  4.並發listenableFutrue回調處理,以下是所有的功能:

1. 基本工具 [Basic utilities]

讓使用Java語言變得更舒適

1.1 使用和避免null:null是模棱兩可的,會引起令人困惑的錯誤,有些時候它讓人很不舒服。很多Guava工具類用快速失敗拒絕null值,而不是盲目地接受

1.2 前置條件: 讓方法中的條件檢查更簡單

1.3 常見Object方法: 簡化Object方法實現,如hashCode()和toString()

1.4 排序: Guava強大的”流暢風格比較器”

1.5 Throwables:簡化了異常和錯誤的傳播與檢查

2. 集合[Collections]

Guava對JDK集合的擴展,這是Guava最成熟和為人所知的部分

2.1 不可變集合: 用不變的集合進行防御性編程和性能提升。

2.2 新集合類型: multisets, multimaps, tables, bidirectional maps等

2.3 強大的集合工具類: 提供java.util.Collections中沒有的集合工具

2.4 擴展工具類:讓實現和擴展集合類變得更容易,比如創建Collection的裝飾器,或實現迭代器

3. 緩存[Caches]

Guava Cache:本地緩存實現,支持多種緩存過期策略

4. 函數式風格[Functional idioms]

Guava的函數式支持可以顯著簡化代碼,但請謹慎使用它

5. 並發[Concurrency]

強大而簡單的抽象,讓編寫正確的並發代碼更簡單

5.1 ListenableFuture:完成后觸發回調的Future

5.2 Service框架:抽象可開啟和關閉的服務,幫助你維護服務的狀態邏輯

6. 字符串處理[Strings]

非常有用的字符串工具,包括分割、連接、填充等操作

7. 原生類型[Primitives]

擴展 JDK 未提供的原生類型(如int、char)操作, 包括某些類型的無符號形式

8. 區間[Ranges]

可比較類型的區間API,包括連續和離散類型

9. I/O

簡化I/O尤其是I/O流和文件的操作,針對Java5和6版本

10. 散列[Hash]

提供比Object.hashCode()更復雜的散列實現,並提供布魯姆過濾器的實現

11. 事件總線[EventBus]

發布-訂閱模式的組件通信,但組件不需要顯式地注冊到其他組件中

12. 數學運算[Math]

優化的、充分測試的數學工具類

13. 反射[Reflection]

Guava 的 Java 反射機制工具類

 

1.Guava EventBus探討

在設計模式中, 有一種叫做發布/訂閱模式, 即某事件被發布, 訂閱該事件的角色將自動更新。
那么訂閱者和發布者直接耦合, 也就是說在發布者內要通知訂閱者說我這邊有東西發布了, 你收一下。

Observable.just(1).subscribe(new Subsriber(){

    @Override
    public void onCompleted() {
    System.out.println("onCompleted ");
    }

    @Override
    public void onError(Throwable arg0) {
    }

    @Override
    public void onNext(Long arg0) {
        System.out.println("onNext " + arg0);
    }
})

我們可以看到, 發布者發布一個數字1, 訂閱者訂閱了這個

而加入eventBus, 發布者與生產者之間的耦合性就降低了。因為這時候我們去管理eventbus就可以, 發布者只要向eventbus發送信息就可以, 而不需要關心有多少訂閱者訂閱了此消息。模型如下

 

為什么說eventBus 是單塊架構的利器呢?

首先單塊架構就是在一個進程內, 在一個進程內, 我們還是希望模塊與模塊之間(功能與功能之間)是松耦合的,而在一個模塊中是高度內聚的, 如何降低一定的耦合, 使得代碼更加有結構, guava eventbus就是支持進程內通訊的橋梁。

想象一下以下業務

我們希望在數據到來之后, 進行入庫, 同時能夠對數據進行報警預測, 當發生報警了, 能夠有以下幾個動作, 向手機端發送推送, 向web端發送推送, 向手機端發送短信。

在一般情況下我們可以這樣實現: (偽代碼如下)

processData(data){
    insertintoDB(data); //執行入庫操作
    predictWarning(data);   // 執行報警預測
}
在predictWarning(data)中{
    if(data reaches warning line){
        sendNotification2App(data); //向手機端發送推送
        sendNotification2Web(data); // 向web端發送推送
        sendSMS2APP(data);      //手機端發送短信
    }
}
在這里我不去講具體是如何向web端發送推送, 如何發送短信。主要用到第三方平台

分析

入庫和報警預測是沒有直接聯系,或者是不分先后順序的, 同樣在報警模塊中, 向3個客戶端發送信息也應該是沒有聯系的, 所以以上雖然可以實現功能, 但不符合代碼的合理性。

應該是怎么樣的邏輯呢? 如下圖

 

 

當數據事件觸發, 發布到data EventBus 上, 入庫和預警分別訂閱這個eventBus, 就會觸發這兩個事件, 而在預警事件中, 將事件發送到warning EventBus 中, 由下列3個訂閱的客戶端進行發送消息。

如何實現

先來講同步, 即訂閱者收到事件后依次執行, 下面都是偽代碼, 具體的入庫細節等我在這里不提供。

@Component
public class DataHandler{
    
    @Subscribe
    public void handleDataPersisist(Data data){
        daoImpl.insertData2Mysql(data);
    }
    
    @Subscribe
    public void predictWarning(Data data){
        if(data is warning){ // pseudo code  如果預警
            Warning warning = createWarningEvent(data);  // 根據data創建一個Warning事件
            postWarningEvent(warning)
        }
    }
    
    protected postWarningEvent(Warning warning){
        EventBusManager.warningEventBus.post(warning);// 發布到warning event 上
    }
    
    @PostConstruct   // 由spring 在初始化bean后執行
    public void init(){
        register2DataEventBus();
    }
    
    // 將自己注冊到eventBus中
    protected void register2DataEventBus(){
        EventBusManager.dataEventBus.register(this);
    }
    
}

@Component
public class WarningHandler{
    @Subscribe
    public void sendNotification2AppClient(Warning warning){
        JpushUtils.sendNotification(warning);
    }
    @Subscribe
    public void sendSMS(Warning warning){
        SMSUtils.sendSMS(warning);
    }
    @Subscribe
    public void send2WebUsingWebSocket(Warning warning){
        WebsocketUtils.sendWarning(warning);
    }
    
    @PostConstruct   // 由spring 在初始化bean后執行
    public void init(){
        register2WarningEventBus();
    }
    
    // 將自己注冊到eventBus中
    protected void register2DataEventBus(){
        EventBusManager.warningEventBus.register(this);
    }
}


/**
 * 管理所有的eventBus
 **/
public class EventBusManager{
    public final static EventBus dataEventBus = new EventBus();
    public final static EventBus warningEventBus = new EventBus();
    
}



簡化
// 我們發現每一個Handler都要進行注冊,
public abstract class BaseEventBusHandler{
    
    @PostConstruct
    public void init(){
        register2EventBus();
    }
    private void register2EventBus(){
        getEventBus().register(this);
    }
    protected abstract EventBus getEventBus();
}
這樣在寫自己的eventBus只需要

@Component
public class MyEventBus extends BaseEventBusHandler{
    @Override
    protected abstract EventBus getEventBus(){
        retrun EventBusManager.myEventBus;
    }
}

在目前的應用場景下, 同步是我們不希望的, 異步場景也很容易實現。
只需要將EventBus 改成
 AsyncEventBus warningEvent = new AsyncEventBus(Executors.newFixedThreadPool(1))
 AsyncEventBus dataEventBus = new AsyncEventBus(Executors.newFixedThreadPool(3))

 

2.集合處理

1.optional空值比較

2.集合排序guava

    Integer[] inumber={55,22,33};
        System.out.println(new Ordering<Integer>(){
            @Override
            public int compare(Integer left, Integer right) {
                return left-right;
            }
        }.sortedCopy(Arrays.asList(inumber)));

//java 需要自定義compare

 

3.guava cache 緩存觸發機制

業務場景,當某一個文件保留的有效期30分鍾后刪除;某一個文件容易超過一定限定。

基於容量的回收:

規定緩存項的數目不超過固定值,只需使用CacheBuilder.maximumSize(long)。緩存將嘗試回收最近沒有使用或總體上很少使用的緩存項。—— 警告:在緩存項的數目達到限定值之前,即緩存項的數目逼近限定值時緩存就可能進行回收操作。這個size指的是cache中的條目數,不是內存大小或是其他.
public class GuavaCacheTest {
    public static void main(String[] args) {
        Cache<Integer, String> cache = CacheBuilder.newBuilder().maximumSize(2).build();
        cache.put(1, "a");
        cache.put(2, "b");
        cache.put(3, "c");
        System.out.println(cache.asMap());
        System.out.println(cache.getIfPresent(2));
        cache.put(4, "d");
        System.out.println(cache.asMap());
    }
}

基於時間的回收

guava 提供兩種定時回收的方法

expireAfterAccess(long, TimeUnit):緩存項在給定時間內沒有被讀/寫訪問,則回收。請注意這種緩存的回收順序和基於大小回收一樣。

expireAfterWrite(long, TimeUnit):緩存項在給定時間內沒有被寫訪問(創建或覆蓋),則回收。如果認為緩存數據總是在固定時候后變得陳舊不可用,這種回收方式是可取的。

public class GuavaCacheTest {
    public static void main(String[] args) {
        LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).removalListener(new RemovalListener<Object, Object>() {
            @Override
            public void onRemoval(RemovalNotification<Object, Object> notification) {
                System.out.println("remove key[" + notification.getKey() + "],value[" + notification.getValue() + "],remove reason[" + notification.getCause() + "]");
            }
        }).recordStats().build(
                new CacheLoader<Integer, Integer>() {
                    @Override
                    public Integer load(Integer key) throws Exception {
                        return 2;
                    }
                }
        );
        cache.put(1, 1);
        cache.put(2, 2);
        System.out.println(cache.asMap());
        cache.invalidateAll();
        System.out.println(cache.asMap());
        cache.put(3, 3);
        try {
            System.out.println(cache.getUnchecked(3));
            Thread.sleep(4000);
            System.out.println(cache.getUnchecked(3));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Cache<Integer, Integer> cache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(3, TimeUnit.SECONDS).build();

 4.ListenableFuture 異步回調通知

傳統JDK中的Future通過異步的方式計算返回結果:在多線程運算中可能或者可能在沒有結束返回結果,Future是運行中的多線程的一個引用句柄,確保在服務執行返回一個Result。

ListenableFuture可以允許你注冊回調方法(callbacks),在運算(多線程執行)完成的時候進行調用,  或者在運算(多線程執行)完成后立即執行。這樣簡單的改進,使得可以明顯的支持更多的操作,這樣的功能在JDK concurrent中的Future是不支持的。

ListenableFuture 中的基礎方法是addListener(Runnable, Executor), 該方法會在多線程運算完的時候,指定的Runnable參數傳入的對象會被指定的Executor執行。

添加回調(Callbacks)

多數用戶喜歡使用 Futures.addCallback(ListenableFuture<V>, FutureCallback<V>, Executor)的方式, 或者 另外一個版本version(譯者注:addCallback(ListenableFuture<V> future,FutureCallback<? super V> callback)),默認是采用 MoreExecutors.sameThreadExecutor()線程池, 為了簡化使用,Callback采用輕量級的設計.  FutureCallback<V> 中實現了兩個方法:

  • onSuccess(V),在Future成功的時候執行,根據Future結果來判斷。
  • onFailure(Throwable), 在Future失敗的時候執行,根據Future結果來判斷。

ListenableFuture的創建

對應JDK中的 ExecutorService.submit(Callable) 提交多線程異步運算的方式,Guava 提供了ListeningExecutorService 接口, 該接口返回 ListenableFuture 而相應的 ExecutorService 返回普通的 Future。將 ExecutorService 轉為 ListeningExecutorService,可以使用MoreExecutors.listeningDecorator(ExecutorService)進行裝飾。

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
ListenableFuture explosion = service.submit(new Callable() {
  public Explosion call() {
    return pushBigRedButton();
  }
});
Futures.addCallback(explosion, new FutureCallback() {
  // we want this handler to run immediately after we push the big red button!
  public void onSuccess(Explosion explosion) {
    walkAwayFrom(explosion);
  }
  public void onFailure(Throwable thrown) {
    battleArchNemesis(); // escaped the explosion!
  }
});

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM