SpringBoot2.1版本的我的應用開發框架 - 集成Redis緩存

本篇做爲SpringBoot2.1版本的我的開發框架 子章節,請先閱讀SpringBoot2.1版本的我的開發框架再次閱讀本篇文章html

項目地址:SpringBoot2.1版本的我的應用開發框架前端

前端項目地址:ywh-vue-adminvue

關於爲何要用Redis?

項目中爲何要用Redis緩存?其實在我實習時是用到過Redis緩存的,可是我只是知道用到了Redis,如何使用的,爲何要用,這些我通通都不知道。引用網上一句話就是,我此次集成也是爲了Redis而Redis了,並非拿Redis來解決實際的問題,可是我以爲只有先學會了,才能知道在什麼狀況下能夠用Redis來解決問題。java

爲何要用redis?引用網上的言論git

1.解決應用服務器的cpu和內存壓力 2.在項目中使用 Redis,主要考慮兩個角度:性能和併發。 3.咱們在碰到須要執行耗時特別久,且結果不頻繁變更的 SQL,就特別適合將運行結果放入緩存。這樣,後面的請求就去緩存中讀取,使得請求可以迅速響應。 4.在大併發的狀況下,全部的請求直接訪問數據庫,數據庫會出現鏈接異常。這個時候,就須要使用redis作一個緩衝操做,讓請求先訪問到redis,而不是直接訪問數據庫。 5.排行榜及相關問題。排行榜(leader board)按照得分進行排序。zadd命令能夠直接實現這個功能,而zrevrange命令能夠用來按照得分來獲取前100名的用戶,zrank能夠用來獲取用戶排名,很是直接並且操做容易。 6.計數的問題,好比點贊和轉發數,經過原子遞增保持計數;getset用來重置計數器;過時屬性用來確認一個關鍵字何時應該刪除。github

個人學習筆記:redis

並且Redis能夠master-slave(主從)模式進行數據備份,在分佈式的系統中,能夠很好保證數據的備份,Redis會自動把主數據庫(master)中的數據備份到從數據庫(slave)中,關於爲何要用Redis這件事情上,除了自己本身項目中的緣由;剩下的有不少緣由均可以事先在網絡上汲取知識以及經驗。json

SpringBoot的緩存支持

咱們把緩存的事情放到ywh-starter-cache這個模塊中來集成,在cache中的pom.xml文件中引入spring-boot-starter-data-redis依賴

<!-- redis依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>
複製代碼

cache項目結構分爲

  • config - 放配置類
  • serializer - 放序列化類
  • utils - 放工具類

源碼級分析

爲何用戶須要本身建立一個redis的配置類呢?

SpringBoot提供了對Redis的自動配置功能,在RedisAutoConfiguration類中默認爲咱們配置了客戶端鏈接(Lettuce和Jedis),以及數據操做模板(StringRedisTemplate和RedisTemplate),下列代碼有一個@ConditionalOnMissingBean和@Bean的註解,@ConditionalOnMissingBean註解判斷是否執行初始化代碼,即若是用戶已經建立了bean,則相關的初始化代碼再也不執行。這致使了默認的是redisTemplate方法會被執行。

public class RedisAutoConfiguration {
    public 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;
    }
}
複製代碼

RedisTemplate這個數據操做模板類咱們能夠點擊去看一看,在類中有一段代碼

if (this.defaultSerializer == null) {
    this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
複製代碼

若是默認的序列化爲空則使用jdk來序列化咱們的數據,而 defaultSerializer 這個私有屬性,默認爲NULL,因此默認的序列方式時jdk的方式,可是這個序列化方式會把數據變得人看不懂,因此才須要建立一個Redis配置類覆蓋默認的序列化,若是我有分析的有不對的地方,望指正。

簡單的鏈接

分析事後,就以兩種方式來進行鏈接。第一種:不更改默認配置使用StringRedisTemplate和RedisTemplate

在咱們寫代碼測試以前須要在cache模塊中配置application-redis.yml文件,而且把core下的application.yml文件中的active屬性添加redis 以逗號相隔這樣就可在運行的時候讀取application-redis.yml的內容,把cache模塊下application.properties修改爲application-redis.yml文件進行配置。

spring:
 redis:
    # Redis數據庫索引(默認爲0)
 database: 0
    # Redis服務器地址
 host: 127.0.0.1
    # Redis服務器鏈接端口
 port: 6379
    # Redis服務器鏈接密碼(默認爲空)若是沒有配置密碼就不要寫這個屬性了
 password: 123456
    #鏈接池
 lettuce:
 pool:
        #鏈接池最大鏈接數(使用負值表示沒有限制)
 max-active: 8
        #鏈接池最大阻塞等待時間(使用負值表示沒有限制)
 max-wait: 60000
        #鏈接池中的最大空閒鏈接
 max-idle: 8
        #鏈接池中的最小空閒鏈接
 min-idle: 0
    #鏈接超時時間(毫秒)
 timeout: 10000
複製代碼

在SpringBoot測試類中編寫代碼並運行添加數據。

@Autowired
private StringRedisTemplate stringRedisTemplate;

/** * 測試鏈接redis,並存入數據 */
@Test
public void redisTest(){
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    stringRedisTemplate.opsForValue().set("abc","測試");
    stringRedisTemplate.opsForList().leftPushAll("ywh",list);

}
複製代碼

StringRedis測試數據

咱們在用StringRedisTemplate添加的數據顯示正常,也是咱們人眼能讀懂的,接下來咱們用RedisTemplate這個類來鏈接Redis添加數據看一看數據是什麼樣的,默認的序列化JdkSerializationRedisSerializer的二進制數據序列化方式,代碼跟上面差很少。

@Autowired
private RedisTemplate<String,Object> redisTemplate;

@Test
public void redisTest1(){
    List<String> list = new ArrayList<>();
    list.add("y");
    list.add("w");
    list.add("h");
    redisTemplate.opsForValue().set("redisTemplate","鏈接成功了");
    redisTemplate.opsForList().leftPushAll("redis",list);
}
複製代碼

RedisTemplate測試數據

果真添加的數據咱們人眼分辨不出來這是什麼,因此下面咱們要進行覆蓋默認的配置,定製本身的序列化方式。

覆蓋默認配置進行鏈接

spring爲咱們提供了多種序列化方式,都在org.springframework.data.redis.serializer包下,經常使用的分別是:

  • JdkSerializationRedisSerializer
  • GenericJackson2JsonRedisSerializer
  • StringRedisSerializer
  • Jackson2JsonRedisSerializer

這四種咱們只使用StringRedisSerializer來序列化Key值,value值由咱們本身建立的序列化類,serializer包下建立咱們自定義的FastJsonRedisSerializer類,須要實現RedisSerializer接口,實現接口中的序列化方法和反序列化方法,使用的是alibaba的Fastjson實現。

package com.ywh.cache.serializer;

/** * CreateTime: 2018-12-19 16:51 * ClassName: FastJsonRedisSerializer * Package: com.ywh.cache.serializer * Describe: * 自定義的序列化類 * @author YWH */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    private ObjectMapper objectMapper = new ObjectMapper();
 
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
    private Class<T> clazz;
 
    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }
 
    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }
 
    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
 
        return JSON.parseObject(str, clazz);
    }
    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }
 
    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}
複製代碼

建立好自定義的序列化類後,進行覆蓋默認的配置,接下來在config包下建立RedisCacheConfig類,配置好Redis配置類之後咱們再次從新運行測試類,會發現值再也不是亂碼了。

package com.ywh.cache.config;
 
/** * CreateTime: 2018-12-18 23:34 * ClassName: RedisCacheConfig * Package: com.ywh.cache.config * Describe: * Redis緩存配置 @EnableCaching 開啓聲明式緩存支持. 以後就可使用 @Cacheable/@CachePut/@CacheEvict 註解緩存數據. * @author YWH */
@Configuration
@EnableCaching
public class RedisCacheConfig {
 
 
    private RedisConnectionFactory redisConnectionFactory;
 
    @Autowired
    public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory){
        this.redisConnectionFactory = redisConnectionFactory;
    }
 
    /** * 覆蓋默認的配置 * @return RedisTemplate */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
 
 
        // 設置value的序列化規則和key的序列化規則
        template.setKeySerializer(stringRedisSerializer);
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        template.setDefaultSerializer(fastJsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
 
    /** * 解決註解方式存放到redis中的值是亂碼的狀況 * @param factory 鏈接工廠 * @return CacheManager */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
 
        // 配置註解方式的序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration =
                config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
                        //配置註解默認的過時時間
                        .entryTtl(Duration.ofDays(1));
        // 加入白名單 https://github.com/alibaba/fastjson/wiki/enable_autotype
        ParserConfig.getGlobalInstance().addAccept("com.ywh");
        ParserConfig.getGlobalInstance().addAccept("com.baomidou");
        return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
    }
}
複製代碼

Redis工具類

redis能夠對五種類型操做,能夠本身再進行擴展,工具類咱們放在cache模塊下建立utils包,建立RedisUtil工具類,類上添加@Component註解,交給Spring來管理,咱們能夠直接在其餘的方法中用@AtuoWired獲取。

package com.ywh.cache.utils;
/** * CreateTime: 2018-12-19 17:45 * ClassName: RedisUtil * Package: com.ywh.cache.utils * Describe: * Redis工具類 * * @author YWH */
@Component
public class RedisUtil {
 
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
 
    //---------------------- common --------------------------
 
    /** * 指定緩存失效時間 * @param key key值 * @param time 緩存時間 * @return true設置成功,time <=0 設置失敗 */
    public void expire(String key, long time){
        if(time > 0){
            redisTemplate.expire(key,time,TimeUnit.SECONDS);
        }else{
            throw MyExceptionUtil.mxe("設置的時間不能爲0或者小於0!!");
        }
    }
 
    /** * 判斷key是否存在 * @param key * @return true 存在 false 不存在 */
    public Boolean existsKey(String key){
        return redisTemplate.hasKey(key);
    }
 
    。。。。省略代碼,具體代碼可前往github查看
 
}
複製代碼
相關文章
相關標籤/搜索