国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Spring整合Lettuce自定義緩存簡單實現

DirtyMind / 3175人閱讀

摘要:于是,在這里,我稍微往回走一點,研究一下從版本出現的自定義緩存實現機制,并使用效率更高的連接,實現方法級自定義緩存。

0. 前言

Spring框架提供了一系列豐富的接口幫助我們更快捷的開發應用程序,很多功能僅需要在配置文件聲明一下或者在代碼寫幾行就能夠實現了,能夠使我們更注重于應用的開發上,某種意義上助長了我們的“偷懶”行為。關于緩存,很多時候我們使用Hibernate或Mybatis框架的二級緩存結合Ehcache緩存框架來提高執行效率,配置使用起來也很簡單;又或者使用Redis內存型數據庫,利用Jedis連接操作數據在內存中的讀寫,同樣用起來也很簡單。

然而上述兩種方式的緩存,前者的范圍太廣(如Mybatis是mapper級別的緩存),后者又太細(字符串型的鍵值對)。于是,在這里,我稍微往回走一點,研究一下Spring從3.1版本出現的自定義緩存實現機制,并使用效率更高的Lettuce連接Redis,實現方法級自定義緩存。即用Lettuce做Redis的客戶端連接,使用Redis作為底層的緩存實現技術,在應用層或數據層的方法使用Spring緩存標簽進行數據緩存,結合Redis的可視化工具還可以看到緩存的數據信息。

1.1部分可能相當一部分人都認識,那就重點看下1.2部分的,歡迎指點。

1. 技術準備

涉及技術:

Spring 3.x 緩存注解

Lettuce 4.x Redis連接客戶端

Redis 3.x +

Spring 3.x +

序列化和反序列化

1.1 Spring 3.x 緩存注解

Spring 緩存注解,即Spring Cache作用在方法上。當我們在調用一個緩存方法時會把該方法參數返回結果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。所以在使用Spring Cache的時候我們要保證我們緩存的方法對于相同的方法參數要有相同的返回結果。

要使用Spring Cache,我們需要做兩件事:

在需要緩存的方法上使用緩存注解;

在配置文件聲明底層使用什么做緩存。

對于第一個問題,這里我就不做介紹了,網上已經有十分成熟的文章供大家參考,主要是@Cacheable、@CacheEvict、@CachePut以及自定義鍵的SpEL(Spring 表達式語言,Spring Expression Language)的使用,相信部分人有從Spring Boot中了解過這些東西,詳細可參考以下文章:

Spring緩存注解@Cache,@CachePut , @CacheEvict,@CacheConfig使用 - CSDN博客

Spring緩存注解@Cacheable、@CacheEvict、@CachePut使用 - fashflying - 博客園

對于第二個問題,簡單的說下,知道的可以跳過,這里有三種配置方法:

使用Spring自帶的緩存實現

使用第三方的緩存實現

使用自定緩存實現

無論哪種配置方法都是在Spring的配置文件進行配置的(不要問我Spring的配置文件是什么)。首先,由于我們使用的是注解的方式,對Spring不陌生的話,都知道應該要配置個注解驅動,代碼如下:

    
    

cache-manager屬性的默認值是cacheManager,所以可以顯示寫出,在后續的CacheManage實現類的配置中使用是cacheManager作為id即可。

還有個屬性proxy-target-class,默認為false,因為我們編程經常使用接口,而注解也可能用到接口上,當使用缺省配置時,注解用到接口還是類上都沒有問題,但如果proxy-target-class聲明為true,就只能用到類上了。

三種方法的不同體現在CacheManager類以及Cache類的實現上:

Spring自帶一個SimpleCacheManager的實現,配合ConcurrentMap的配置方法如下:

    
      
         
            
                 
            
         
      
    

使用第三方的緩存實現,如常用的EhCache:

      
          
              
          
      
      
              
      

自定義緩存實現,這里著重講,配制方法與第一種類似,只不過實際的使用的CacheManager類以及Cache類由我們自己定義。實現CacheManager有兩種方式,一是直接實現org.springframework.cache.CacheManager請輸入代碼`接口,該接口有兩個方法:

public interface CacheManager {
    /**
     * Return the cache associated with the given name.
     * @param name the cache identifier (must not be {@code null})
     * @return the associated cache, or {@code null} if none found
     */
    Cache getCache(String name);

    /**
     * Return a collection of the cache names known by this manager.
     * @return the names of all caches known by the cache manager
     */
    Collection getCacheNames();

}

很直白易懂的兩個方法,根據Cache的名字獲取CaChe以及獲取所有Cache的名字,恰當利用好在配置文件中配置Cache時,對相應的name及實現的Cache類進行注入,在CacheManager的實現中使用成員變量,如簡單的HashMap對實現的Cache進行保存即可,對Spring比較熟悉的話,其實非常的簡單,當然,可以根據業務需求實現自己的邏輯,這里只是簡單舉例。另一種方式是繼承抽象類org.springframework.cache.support.AbstractCacheManager,觀看源碼可發現,這是提供了一些模板方法、實現了CacheManager接口的模板類,,只需要實現抽象方法protected abstract Collection loadCaches();即可,下面給出我自己的一個簡單實現(觀看源碼后驚奇的發現與SimpleCacheManager的實現一模一樣):

import java.util.Collection;

import org.springframework.cache.Cache;
import org.springframework.cache.support.AbstractCacheManager;

public class RedisCacheManager extends AbstractCacheManager {

    private Collection caches;

    public void setCaches(Collection caches) {
        this.caches = caches;
    }

    @Override
    protected Collection loadCaches() {
        return this.caches;
    }
}

說完CacheManager,自然到了Cache的實現,方法就是直接實現Spring的接口org.springframework.cache.Cache,接口的方法有點多,網上也有不少相關文章,這里我只說下自己的看法,代碼如下:

    // 簡單直白,就是獲取Cache的名字
    String getName();

    // 獲取底層的緩存實現對象
    Object getNativeCache();

    // 根據鍵獲取值,把值包裝在ValueWrapper里面,如果有必要可以附加額外信息
    ValueWrapper get(Object key);

    // 和get(Object key)類似,但返回值會被強制轉換為參數type的類型,但我查了很多文章,
    // 看了源碼也搞不懂是怎么會觸發這個方法的,取值默認會觸發get(Object key)。
     T get(Object key, Class type);

    // 從緩存中獲取 key 對應的值,如果緩存沒有命中,則添加緩存,
    // 此時可異步地從 valueLoader 中獲取對應的值(4.3版本新增)
    // 與緩存標簽中的sync屬性有關
     T get(Object key, Callable valueLoader);

    // 存放鍵值對
    void put(Object key, Object value);

    // 如果鍵對應的值不存在,則添加鍵值對
    ValueWrapper putIfAbsent(Object key, Object value);

    // 移除鍵對應鍵值對
    void evict(Object key);

    // 清空緩存
    void clear();

下面給出的實現不需要用到 T get(Object key, Class type); T get(Object key, Callable valueLoader);,只是簡單的輸出一句話(事實上也沒見有輸出過)。另外存取的時候使用了序列化技術,序列化是把對象轉換為字節序列的過程,對實際上是字符串存取的Redis來說,可以把字節當成字符串存儲,這里不詳述了,當然也可以使用GSON、Jackson等Json序列化類庫轉換成可讀性高的Json字符串,不過很可能需要緩存的每個類都要有對應的一個Cache,可能會有十分多的CaChe實現類,但轉換效率比JDK原生的序列化效率高得多,另外也可以使用簡單的HashMap,方法很多,可以自己嘗試。

說多一句,由于使用Lettuce連接,redis連接對象的操作和jedis或redisTemplate不同,但理解起來不難。

import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.concurrent.Callable;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;

import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.api.sync.RedisCommands;

public class RedisCache implements Cache {


    private String name;
    private static JdkSerializationRedisSerializer redisSerializer;

    @Autowired
    private StatefulRedisConnection redisConnection;

    public RedisCache() {
        redisSerializer = new JdkSerializationRedisSerializer();
        name = RedisCacheConst.REDIS_CACHE_NAME;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Object getNativeCache() {
        // 返回redis連接看似奇葩,但redis連接就是操作底層實現緩存的對象
        return getRedisConnection();
    }

    @Override
    public ValueWrapper get(Object key) {
        RedisCommands redis = redisConnection.sync();
        String redisKey = (String) key;

        String serializable = redis.get(redisKey);
        if (serializable == null) {
            System.out.println("-------緩存不存在------");
            return null;
        }
        System.out.println("---獲取緩存中的對象---");
        Object value = null;
        // 序列化轉化成字節時,聲明編碼RedisCacheConst.SERIALIZE_ENCODE(ISO-8859-1),
        // 否則轉換很容易出錯(編碼為UTF-8也會轉換錯誤)
        try {
            value = redisSerializer
                    .deserialize(serializable.getBytes(RedisCacheConst.SERIALIZE_ENCODE));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return new SimpleValueWrapper(value);

    }

    @Override
    public  T get(Object key, Class type) {
        System.out.println("------未實現get(Object key, Class type)------");
        return null;
    }

    @Override
    public  T get(Object key, Callable valueLoader) {
        System.out.println("---未實現get(Object key, Callable valueLoader)---");
        return null;
    }

    @Override
    public void put(Object key, Object value) {
        System.out.println("-------加入緩存------");
        RedisCommands redis = redisConnection.sync();
        String redisKey = (String) key;
        byte[] serialize = redisSerializer.serialize(value);
        try {
            redis.set(redisKey, new String(serialize, RedisCacheConst.SERIALIZE_ENCODE));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        System.out.println("---未實現putIfAbsent(Object key, Object value)---");
        return null;
    }

    @Override
    public void evict(Object key) {
        System.out.println("-------刪除緩存 key=" + key.toString() + " ------");
        RedisCommands redis = redisConnection.sync();
        String redisKey = key.toString();
        // RedisCacheConst.WILDCARD是Redis中鍵的通配符“*”,用在這里使鍵值刪除也能使用通配方式
        if (redisKey.contains(RedisCacheConst.WILDCARD)) {
            List caches = redis.keys(redisKey);
            if (!caches.isEmpty()) {
                redis.del(caches.toArray(new String[caches.size()]));
            }
        } else {
            redis.del(redisKey);
        }
    }

    @Override
    public void clear() {
        System.out.println("-------清空緩存------");
        RedisCommands redis = redisConnection.sync();
        redis.flushdb();
    }

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

    public StatefulRedisConnection getRedisConnection() {
        return redisConnection;
    }

    public void setRedisConnection(StatefulRedisConnection redisConnection) {
        this.redisConnection = redisConnection;
    }
}

RedisCacheConst常量類

public class RedisCacheConst {
    public final static String REDIS_CACHE_NAME = "Redis Cache";
    public final static String SERIALIZE_ENCODE = "ISO-8859-1";
    public final static String WILDCARD = "*";
    public final static String SPRING_KEY_TAG = """;
    // SpEL中普通的字符串要加上單引號,如一個鍵設為kanarien,應為key=""kanarien""
}

Spring配置文件

    
    

    
    
        
               
                
            
        
    
1.2 Lettuce 4.x Redis連接客戶端

1.1部分講的有點多了,我真正想講的也就是自定義那部分,但其他部分也不能不說,咳咳。

Lettuce,在Spring Boot 2.0之前幾乎沒怎么聽說過的詞語,自Spring Boot 2.0漸漸進入國人的視野(Lettuce 5.x),因為Spring Boot 2.0默認采用Lettuce 5.x + Redis 方式實現方法級緩存,很多文章都有這么強調過。Lettuce為什么會受到Spring Boot開發人員的青睞呢?簡單說來,Lettuce底層使用Netty框架,利用NIO技術,達到線程安全的并發訪問,同時有著比Jedis更高的執行效率與連接速度

Lettuce還支持使用Unix Domain Sockets,這對程序和Redis在同一機器上的情況來說,是一大福音。平時我們連接應用和數據庫如Mysql,都是基于TCP/IP套接字的方式,如127.0.0.1:3306,達到進程與進程之間的通信,Redis也不例外。但使用UDS傳輸不需要經過網絡協議棧,不需要打包拆包等操作,只是數據的拷貝過程,也不會出現丟包的情況,更不需要三次握手,因此有比TCP/IP更快的連接與執行速度。當然,僅限Redis進程和程序進程在同一主機上,而且僅適用于Unix及其衍生系統

事實上,標題中所說的簡單實現,適用于中小項目,因為中小項目不會花太多資源在硬件上,很可能Redis進程和程序進程就在同一主機上,而我們所寫的程序只需要簡單的實現就足夠了,本篇文章介紹的東西都適用于中小項目的,而且正因為簡單才易于去剖析源碼,邊寫邊學。

另外,為什么這里說的是Lettuce 4.x而不是Lettuce 5.x呢?
因為我寫項目那時還沒Lettuce 5.x啊,只是寫這篇文章有點晚了,技術日新月異啊。4和5之間的差別還是挺大的,代碼中對Redis連接方式就變了(好像?),之后再去研究下。詳細可見官方文檔,這里不再班門弄斧了。

Lettuce官網

下面是Lettuce 4.x的客戶端連接代碼(兼用TCP/IP與UDS連接方式,后者不行自動轉前者),由于涉及了邏輯判斷,使用了Java類進行配置而不是在xml中配置:

import java.nio.file.Files;
import java.nio.file.Paths;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.RedisURI.Builder;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.resource.ClientResources;
import com.lambdaworks.redis.resource.DefaultClientResources;

@Primary
@Configuration
public class LettuceConfig {

    private static RedisURI redisUri;

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Value("${redis.host:127.0.0.1}")
    private String hostName;

    @Value("${redis.domainsocket:}")
    private String socket;

    @Value("${redis.port:6379}")
    private int port;

    private int dbIndex = 2;

    @Value(value = "${redis.pass:}")
    private String password;

    @Bean(destroyMethod = "shutdown")
    ClientResources clientResources() {
        return DefaultClientResources.create();
    }

    @Bean(destroyMethod = "close")
    StatefulRedisConnection redisConnection(RedisClient redisClient) {
        return redisClient.connect();
    }

    private RedisURI createRedisURI() {
        Builder builder = null;
    // 判斷是否有配置UDS信息,以及判斷Redis是否有支持UDS連接方式,是則用UDS,否則用TCP
        if (StringUtils.isNotBlank(socket) && Files.exists(Paths.get(socket))) {
            builder = Builder.socket(socket);
            System.out.println("connect with Redis by Unix domain Socket");
            log.info("connect with Redis by Unix domain Socket");
        } else {
            builder = Builder.redis(hostName, port);
            System.out.println("connect with Redis by TCP Socket");
            log.info("connect with Redis by TCP Socket");
        }
        builder.withDatabase(dbIndex);
        if (StringUtils.isNotBlank(password)) {
            builder.withPassword(password);
        }
        return builder.build();
    }

    @PostConstruct
    void init() {
        redisUri = createRedisURI();
        log.info("連接Redis成功!
 host:" + hostName + ":" + port + " pass:" + password + " dbIndex:" + dbIndex);
    }

    @Bean(destroyMethod = "shutdown")
    RedisClient redisClient(ClientResources clientResources) {
        return RedisClient.create(clientResources, redisUri);
    }

    public void setDbIndex(int dbIndex) {
        this.dbIndex = dbIndex;
    }

    public void setHostName(String hostName) {
        this.hostName = hostName;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setSocket(String socket) {
        this.socket = socket;
    }

}

Java屬性文件:redis.properties(僅供參考)

redis.pool.maxIdle=100
redis.pool.maxTotal=10
redis.pool.testOnBorrow=true
redis.pool.testOnReturn=true
redis.host=127.0.0.1
#redis.pass=yourpassword
redis.port=6379
redis.expire=6000
redis.domainsocket=/tmp/redis.sock

注解用得很多,說明下:

@Primary,表示優先級關系,由于源程序中涉及數據到Redis的加載,所以要設定,視情況可以不加;

@Configuration,表名這是個配置的Bean,能被Spring掃描器識別;

@Value,與Java屬性文件有關,自動讀取屬性文件的值,括號中的內容就是鍵,冒號后面的是默認值;

@PostConstruct,在類加載完、依賴注入完之后才執行所修飾的方法,注意要在Spring配置文件中;

@Bean,不解釋。

最后,該類要被Spring掃描識別。

1.3 Redis 3.x +

關于Redis的介紹,直接去看我的筆記,里面有一些簡單又不失全面的介紹,比如Unix Domain Socket相關、一些Redis的基本配置和可視化界面等等。

Kanarien的Redis筆記

2. 補充

必要的代碼都給出來了,就不貼源碼了,Lettuce的TCP、UDS二選一連接方式也可以多帶帶拿出來用。

歡迎大家的指點!

Copyright ? 2018, GDUT CSCW back-end Kanarien, All Rights Reserved

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71671.html

相關文章

  • 一起來學SpringBoot | 第九篇:整合Lettuce Redis

    摘要:相比它支持存儲的類型相對更多字符哈希集合有序集合列表,同時是線程安全的。基于的連接實例,可以在多個線程間并發訪問,且線程安全,滿足多線程環境下的并發訪問,同時它是可伸縮的設計,一個連接實例不夠的情況也可以按需增加連接實例。 SpringBoot 是為了簡化 Spring 應用的創建、運行、調試、部署等一系列問題而誕生的產物,自動裝配的特性讓我們可以更好的關注業務本身而不是外部的XML...

    yacheng 評論0 收藏0
  • java | Spring Boot 與 Redis 實現 Cache 以及 Session 共享

    摘要:完成狀態編寫中已完成維護中原文是一個使用編寫的開源支持網絡基于內存可選持久性的鍵值對存儲數據庫維基百科是目前業界使用廣泛的基于內存的數據庫。 完成狀態 [ ] 編寫中 [ ] 已完成 [x] 維護中 原文 Redis Redis是一個使用ANSI C編寫的開源、支持網絡、基于內存、可選持久性的鍵值對存儲數據庫 ------ 維基百科 Redis 是目前業界使用廣泛的基于內存的...

    ssshooter 評論0 收藏0
  • 阿里開源的緩存框架JetCache

    摘要:是一個基于的緩存系統封裝,提供統一的和注解來簡化緩存的使用。提供了比更加強大的注解,可以原生的支持兩級緩存分布式自動刷新,還提供了接口用于手工緩存操作。緩存失效時間緩存的類型,包括。 之前一直在用Spring Cache進行接口數據的緩存,主要是Spring Cache在對具體key緩存失效時間的設置不是很方法,還要自己去擴展,無意中發現了阿里的JetCache。大部分的需求都能滿足,...

    MageekChiu 評論0 收藏0

發表評論

0條評論

DirtyMind

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<