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

資訊專欄INFORMATION COLUMN

Spring整合Lettuce自定義緩存簡單實現(xiàn)

DirtyMind / 3178人閱讀

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

0. 前言

Spring框架提供了一系列豐富的接口幫助我們更快捷的開發(fā)應(yīng)用程序,很多功能僅需要在配置文件聲明一下或者在代碼寫幾行就能夠?qū)崿F(xiàn)了,能夠使我們更注重于應(yīng)用的開發(fā)上,某種意義上助長了我們的“偷懶”行為。關(guān)于緩存,很多時候我們使用Hibernate或Mybatis框架的二級緩存結(jié)合Ehcache緩存框架來提高執(zhí)行效率,配置使用起來也很簡單;又或者使用Redis內(nèi)存型數(shù)據(jù)庫,利用Jedis連接操作數(shù)據(jù)在內(nèi)存中的讀寫,同樣用起來也很簡單。

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

1.1部分可能相當(dāng)一部分人都認(rèn)識,那就重點看下1.2部分的,歡迎指點。

1. 技術(shù)準(zhǔn)備

涉及技術(shù):

Spring 3.x 緩存注解

Lettuce 4.x Redis連接客戶端

Redis 3.x +

Spring 3.x +

序列化和反序列化

1.1 Spring 3.x 緩存注解

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

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

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

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

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

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

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

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

使用Spring自帶的緩存實現(xiàn)

使用第三方的緩存實現(xiàn)

使用自定緩存實現(xiàn)

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

    
    

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

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

三種方法的不同體現(xiàn)在CacheManager類以及Cache類的實現(xiàn)上:

Spring自帶一個SimpleCacheManager的實現(xiàn),配合ConcurrentMap的配置方法如下:

    
      
         
            
                 
            
         
      
    

使用第三方的緩存實現(xiàn),如常用的EhCache:

      
          
              
          
      
      
              
      

自定義緩存實現(xiàn),這里著重講,配制方法與第一種類似,只不過實際的使用的CacheManager類以及Cache類由我們自己定義。實現(xiàn)CacheManager有兩種方式,一是直接實現(xiàn)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();

}

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

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的實現(xiàn),方法就是直接實現(xiàn)Spring的接口org.springframework.cache.Cache,接口的方法有點多,網(wǎng)上也有不少相關(guān)文章,這里我只說下自己的看法,代碼如下:

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

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

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

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

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

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

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

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

    // 清空緩存
    void clear();

下面給出的實現(xiàn)不需要用到 T get(Object key, Class type); T get(Object key, Callable valueLoader);,只是簡單的輸出一句話(事實上也沒見有輸出過)。另外存取的時候使用了序列化技術(shù),序列化是把對象轉(zhuǎn)換為字節(jié)序列的過程,對實際上是字符串存取的Redis來說,可以把字節(jié)當(dāng)成字符串存儲,這里不詳述了,當(dāng)然也可以使用GSON、Jackson等Json序列化類庫轉(zhuǎn)換成可讀性高的Json字符串,不過很可能需要緩存的每個類都要有對應(yīng)的一個Cache,可能會有十分多的CaChe實現(xiàn)類,但轉(zhuǎn)換效率比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連接就是操作底層實現(xiàn)緩存的對象
        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;
        // 序列化轉(zhuǎn)化成字節(jié)時,聲明編碼RedisCacheConst.SERIALIZE_ENCODE(ISO-8859-1),
        // 否則轉(zhuǎn)換很容易出錯(編碼為UTF-8也會轉(zhuǎn)換錯誤)
        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("------未實現(xiàn)get(Object key, Class type)------");
        return null;
    }

    @Override
    public  T get(Object key, Callable valueLoader) {
        System.out.println("---未實現(xiàn)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("---未實現(xiàn)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中普通的字符串要加上單引號,如一個鍵設(shè)為kanarien,應(yīng)為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默認(rèn)采用Lettuce 5.x + Redis 方式實現(xiàn)方法級緩存,很多文章都有這么強調(diào)過。Lettuce為什么會受到Spring Boot開發(fā)人員的青睞呢?簡單說來,Lettuce底層使用Netty框架,利用NIO技術(shù),達(dá)到線程安全的并發(fā)訪問,同時有著比Jedis更高的執(zhí)行效率與連接速度

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

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

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

Lettuce官網(wǎng)

下面是Lettuce 4.x的客戶端連接代碼(兼用TCP/IP與UDS連接方式,后者不行自動轉(zhuǎn)前者),由于涉及了邏輯判斷,使用了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,表示優(yōu)先級關(guān)系,由于源程序中涉及數(shù)據(jù)到Redis的加載,所以要設(shè)定,視情況可以不加;

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

@Value,與Java屬性文件有關(guān),自動讀取屬性文件的值,括號中的內(nèi)容就是鍵,冒號后面的是默認(rèn)值;

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

@Bean,不解釋。

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

1.3 Redis 3.x +

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

Kanarien的Redis筆記

2. 補充

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

歡迎大家的指點!

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

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/71671.html

相關(guān)文章

  • 一起來學(xué)SpringBoot | 第九篇:整合Lettuce Redis

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

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

    摘要:完成狀態(tài)編寫中已完成維護中原文是一個使用編寫的開源支持網(wǎng)絡(luò)基于內(nèi)存可選持久性的鍵值對存儲數(shù)據(jù)庫維基百科是目前業(yè)界使用廣泛的基于內(nèi)存的數(shù)據(jù)庫。 完成狀態(tài) [ ] 編寫中 [ ] 已完成 [x] 維護中 原文 Redis Redis是一個使用ANSI C編寫的開源、支持網(wǎng)絡(luò)、基于內(nèi)存、可選持久性的鍵值對存儲數(shù)據(jù)庫 ------ 維基百科 Redis 是目前業(yè)界使用廣泛的基于內(nèi)存的...

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

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

    MageekChiu 評論0 收藏0

發(fā)表評論

0條評論

DirtyMind

|高級講師

TA的文章

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