SpringBoot2.x—SpringCache(1)集成

JAVA && Spring && SpringBoot2.x — 學習目錄

SpringBoot2.x—SpringCache(1)集成
SpringBoot2.x—SpringCache(2)使用
SpringBoot2.x—SpringCache(3) CacheManager源碼

聲明式與編程式

說起SpringCache您可能不清楚。但您絕對清楚事務(wù)氓侧。

一般使用事務(wù)分為編程式和聲明式脊另。

編程式:事務(wù)操作與業(yè)務(wù)代碼耦合,一般我們不會使用這種方式约巷;
聲明式:AOP的運用偎痛,通過注解使得事務(wù)代碼與業(yè)務(wù)代碼解耦,目前項目中一般都是使用事務(wù)注解独郎。

而我們平時使用緩存踩麦,正是編程式,即對緩存的操作與業(yè)務(wù)代碼耦合氓癌。那么是否存在一種類似于事務(wù)的技術(shù)靖榕,完成聲明式的緩存操作呢?

而SpringCahe便可以提供透明化的緩存操作顽铸,即用戶可以使用注解的方式茁计。靈活的操縱緩存。

1. 引入依賴

本篇是SpringCache+Redis的整合谓松。SpringCache只是緩存抽象星压,即具體緩存的操作需要子類實現(xiàn)。

spring-boot-starter-data-redis中實現(xiàn)了SpringCache的抽象接口鬼譬,即我們整合SpringCache+Redis無需自己實現(xiàn)具體緩存娜膘。

        <!--SpringCache的依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
       <!--整合Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

SpringBoot2.X整合Redis緩存可以看這篇文章,因為有個項目在生產(chǎn)環(huán)境中优质,使用lettuce客戶端每隔一段時間連接斷開(初步估計是Redis機房和應(yīng)用服務(wù)器機房網(wǎng)絡(luò)問題)竣贪。切換成了jedis客戶端。

2. SpringCache配置

兩種配置巩螃,一種可以在yml中配置演怎,一種是在代碼中配置,此處推薦在@Configuration中進行配置避乏。

原因一是更加靈活爷耀,在配置CacheManager的Bean時,可以初始化Cache對象拍皮,在項目啟動的時候注冊到CacheManager中歹叮。

import com.galax.Config.serialize.RedisObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableCaching  //開啟緩存跑杭,可以放在啟動類上。
public class RedisSpringCache {

    /**
     * 自定義KeyGenerator咆耿。
     * @return
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            //獲取代理對象的最終目標對象
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
            StringBuilder sb = new StringBuilder();
            sb.append(targetClass.getSimpleName()).append(":");
            sb.append(method.getName()).append(":");
            //調(diào)用SimpleKey的邏輯
            Object key = SimpleKeyGenerator.generateKey(params);
            return sb.append(key);
        };
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        //設(shè)置特有的Redis配置
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        //定制化的Cache為300s
        cacheConfigurations.put("as",customRedisCacheConfiguration(Duration.ofSeconds(300)));
        cacheConfigurations.put("books",customRedisCacheConfiguration(Duration.ofSeconds(300)));
        cacheConfigurations.put("cs",customRedisCacheConfiguration(Duration.ofSeconds(300)));
        //默認超時時間60s
        return RedisCacheManager.builder(connectionFactory).
                transactionAware().   //Cache的事務(wù)支持
                cacheDefaults(customRedisCacheConfiguration(Duration.ofSeconds(60))).
                withInitialCacheConfigurations(cacheConfigurations).   //設(shè)置個性化的Cache配置
                build();
    }


    /**
     * 設(shè)置RedisConfiguration配置
     *
     * @param ttl
     * @return
     */
    public RedisCacheConfiguration customRedisCacheConfiguration(Duration ttl) {
        //設(shè)置序列化格式
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer
                = new Jackson2JsonRedisSerializer<>(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(RedisObjectMapper.redisConfigurationObjectMapper());
        return RedisCacheConfiguration.
                defaultCacheConfig().serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).
                computePrefixWith(cacheName -> cacheName + ":").   //設(shè)置Cache的前綴德谅,默認::
                disableCachingNullValues().   //若返回值為null,則不允許存儲到Cache中
                entryTtl(ttl);  //設(shè)置緩存缺省超時時間
    }
}

注意不要將ObjectMapper加入到Spring容器中萨螺。因為Spring容器中存在一個ObjectMapper窄做,以用于@RequestBodyResponseBody屑迂、RestTemplate等地的序列化和反序列化。

為什么不采用Spring容器的ObjectMapper對象冯键,而要自己設(shè)置是因為Redis配置了objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);屬性惹盼,在序列化時記錄類/屬性的類型,以便在反序列化時得到POJO對象惫确。此屬性詳見——Jackson的ObjectMapper.DefaultTyping.NON_FINAL屬性手报。

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class RedisObjectMapper {

    public static ObjectMapper redisConfigurationObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        //JDK1.8新版時間格式化Model
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        objectMapper.registerModule(javaTimeModule);
        //Date類型禁止轉(zhuǎn)換為時間戳
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        //序列化時格式化時間戳
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        //字段名字開啟駝峰命名法
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        //序列化無public的屬性或方法時,不會拋出異常
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        //序列化時保存對象類型,以便反序列化時直接得到具體POJO
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //非空數(shù)據(jù)才進行格式化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        //針對BigDecimal改化,序列化時掩蛤,不采取科學計數(shù)法
        objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
        //反序列化時,POJO中不含有JSON串的屬性陈肛,不解析該字段揍鸟,并且不會拋出異常
        objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        //反序列化{}時,不拋出異常句旱,而是得到null值
        objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
        return objectMapper;
    }
}

此處可以使用protostuff替換Jackson進行序列化和反序列化阳藻,詳細內(nèi)容請點擊...

3. key的設(shè)置

需要注意的是,SpringCache作為應(yīng)用層的聲明式緩存谈撒。其數(shù)據(jù)結(jié)構(gòu)為Key-Value腥泥。那么設(shè)計一個安全優(yōu)雅的Key,是一個重要的任務(wù)啃匿。

  1. SpringCache官網(wǎng)中蛔外,這樣描述SpringCache默認的KeyGenerator的:
  • 若沒有參數(shù)值被得到,返回SimpleKey.EMPTY(空數(shù)組)溯乒。
  • 若只有一個參數(shù)值被得到夹厌,返回該參數(shù)值的實例。
  • 若多個參數(shù)值被得到裆悄,返回一個包含所有參數(shù)值SimpleKey對象尊流。
  1. 默認的KeyGenerator如何獲取參數(shù)值?
  • 若注解上只是指定cacheName屬性灯帮,SimpleKeyGenerator將獲取所有的參數(shù)值崖技。組成SimpleKey對象逻住。
  • 指定cacheNamekey屬性,并且key的屬性支持SpEL表達式:
  1. 基本形式
@Cacheable(value="cacheName", key="#id")
public ResultDTO method(int id);
  1. 組合形式
@Cacheable(value="cacheName", key="T(String).valueOf(#name).concat('-').concat(#password))
public ResultDTO method(int name, String password);
  1. 對象形式
@Cacheable(value="cacheName", key="#user.id)
public ResultDTO method(User user);
  1. 默認的SimpleKeyGenerator的缺陷

SimpleGenerator只會將參數(shù)值封裝為SimpleKey對象迎献。然后作為Key瞎访,可能會導致不同方法Key沖突。
我們雖然可以使用SpEL表達式獲取類名吁恍、方法名扒秸,在進行拼接。但是需要為每一個注解指定冀瓦,太過于繁雜伴奥。

  1. 自定義KeyGenerator

注解上keyGenerator屬性與key屬性是不共存的,即我們?nèi)敉ㄟ^keyGenerator來自定義我們的Key生成器翼闽,那么就需要將所有的參數(shù)值均進行處理拾徙,而不能指定特定的參數(shù)值
處理。

    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            //獲取代理對象的最終目標對象
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
            StringBuilder sb = new StringBuilder();
            sb.append(targetClass.getSimpleName()).append(":");
            sb.append(method.getName()).append(":");
            //調(diào)用SimpleKey的邏輯
            Object key = SimpleKeyGenerator.generateKey(params);
            return sb.append(key);
        };
    }

使用:

    @Cacheable(value = "book2",keyGenerator = "keyGenerator")
    public Account getAccInfo(String customerId, String accType) {
      //業(yè)務(wù)邏輯
    }

4. 使用

springCache和事務(wù)類型感局,均采用AOP原理尼啡。故它們的注意事項也是相同。

  1. 若一個service中询微,注解方法被調(diào)用崖瞭,則注解不會生效;
  2. 只有訪問修飾符為public的方法撑毛,注解才會生效书聚;

SpringCache的使用請參考——SpringBoot2.x—SpringCache(2)使用

文章參考

SpringBoot2.X整合Redis緩存

https://www.cnblogs.com/zhangjianbin/p/6439206.html

https://www.cnblogs.com/wzdnwyyu/p/11180461.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市藻雌,隨后出現(xiàn)的幾起案子寺惫,更是在濱河造成了極大的恐慌,老刑警劉巖蹦疑,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件西雀,死亡現(xiàn)場離奇詭異,居然都是意外死亡歉摧,警方通過查閱死者的電腦和手機艇肴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叁温,“玉大人再悼,你說我怎么就攤上這事∠サ” “怎么了冲九?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我莺奸,道長丑孩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任灭贷,我火速辦了婚禮温学,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘甚疟。我一直安慰自己仗岖,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布览妖。 她就那樣靜靜地躺著轧拄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讽膏。 梳的紋絲不亂的頭發(fā)上檩电,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音桅打,去河邊找鬼是嗜。 笑死愈案,一個胖子當著我的面吹牛挺尾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播站绪,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼遭铺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恢准?” 一聲冷哼從身側(cè)響起魂挂,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馁筐,沒想到半個月后涂召,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡敏沉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年果正,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盟迟。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡秋泳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出攒菠,到底是詐尸還是另有隱情迫皱,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布辖众,位于F島的核電站卓起,受9級特大地震影響和敬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜既绩,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一概龄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饲握,春花似錦私杜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至笆怠,卻和暖如春铝耻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹬刷。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工瓢捉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人办成。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓泡态,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迂卢。 傳聞我的和親對象是個殘疾皇子某弦,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355