欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot緩存方法返回值的方法詳解

 更新時(shí)間:2023年10月24日 14:02:31   作者:Naylor  
如何緩存方法的返回值?應(yīng)該會(huì)有很多的辦法,這篇文章主要為大家介紹兩個(gè)比較常見并且比較容易實(shí)現(xiàn)的辦法:自定義注解和SpringCache,希望對(duì)大家有所幫助

Why

為什么要對(duì)方法的返回值進(jìn)行緩存呢?

簡(jiǎn)單來(lái)說(shuō)是為了提升后端程序的性能和提高前端程序的訪問速度。減小對(duì)db和后端應(yīng)用程序的壓力。

一般而言,緩存的內(nèi)容都是不經(jīng)常變化的,或者輕微變化對(duì)于前端應(yīng)用程序是可以容忍的。

否則,不建議加入緩存,因?yàn)樵黾泳彺鏁?huì)使程序復(fù)雜度增加,還會(huì)出現(xiàn)一些其他的問題,比如緩存同步,數(shù)據(jù)一致性,更甚者,可能出現(xiàn)經(jīng)典的緩存穿透、緩存擊穿、緩存雪崩問題。

HowDo

如何緩存方法的返回值?應(yīng)該會(huì)有很多的辦法,本文簡(jiǎn)單描述兩個(gè)比較常見并且比較容易實(shí)現(xiàn)的辦法:

  • 自定義注解
  • SpringCache

annotation

整體思路:

第一步:定義一個(gè)自定義注解,在需要緩存的方法上面添加此注解,當(dāng)調(diào)用該方法的時(shí)候,方法返回值將被緩存起來(lái),下次再調(diào)用的時(shí)候?qū)⒉粫?huì)進(jìn)入該方法。其中需要指定一個(gè)緩存鍵用來(lái)區(qū)分不同的調(diào)用,建議為:類名+方法名+參數(shù)名

第二步:編寫該注解的切面,根據(jù)緩存鍵查詢緩存池,若池中已經(jīng)存在則直接返回不執(zhí)行方法;若不存在,將執(zhí)行方法,并在方法執(zhí)行完畢寫入緩沖池中。方法如果拋異常了,將不會(huì)創(chuàng)建緩存

第三步:緩存池,首先需要盡量保證緩存池是線程安全的,當(dāng)然了沒有絕對(duì)的線程安全。其次為了不發(fā)生緩存臃腫的問題,可以提供緩存釋放的能力。另外,緩存池應(yīng)該設(shè)計(jì)為可替代,比如可以絲滑得在使用程序內(nèi)存和使用redis直接調(diào)整。

MethodCache

創(chuàng)建一個(gè)名為MethodCache 的自定義注解

package com.ramble.methodcache.annotation;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MethodCache {
}

MethodCacheAspect

編寫MethodCache注解的切面實(shí)現(xiàn)

package com.ramble.methodcache.annotation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Aspect
@Component
public class MethodCacheAspect {
    private static final Map<String, Object> CACHE_MAP = new ConcurrentHashMap<>();
    @Around(value = "@annotation(methodCache)")
    public Object around(ProceedingJoinPoint jp, MethodCache methodCache) throws Throwable {
        String className = jp.getSignature().getDeclaringType().getSimpleName();
        String methodName = jp.getSignature().getName();
        String args = String.join(",", Arrays.toString(jp.getArgs()));
        String key = className + ":" + methodName + ":" + args;
        // key 示例:DemoController:findUser:[FindUserParam(id=1, name=c7)]
        log.debug("緩存的key={}", key);
        Object cache = getCache(key);
        if (null != cache) {
            log.debug("走緩存");
            return cache;
        } else {
            log.debug("不走緩存");
            Object value = jp.proceed();
            setCache(key, value);
            return value;
        }
    }
    private Object getCache(String key) {
        return CACHE_MAP.get(key);
    }
    private void setCache(String key, Object value) {
        CACHE_MAP.put(key, value);
    }
}
  • Around:對(duì)被MethodCache注解修飾的方法啟用環(huán)繞通知
  • ProceedingJoinPoint:通過(guò)此對(duì)象獲取方法所在類、方法名和參數(shù),用來(lái)組裝緩存key
  • CACHE_MAP:緩存池,生產(chǎn)環(huán)境建議使用redis等可以分布式存儲(chǔ)的容器,直接放程序內(nèi)存不利于后期業(yè)務(wù)擴(kuò)張后多實(shí)例部署

controller

package com.ramble.methodcache.controller;
import com.ramble.methodcache.annotation.MethodCache;
import com.ramble.methodcache.controller.param.CreateUserParam;
import com.ramble.methodcache.controller.param.FindUserParam;
import com.ramble.methodcache.service.DemoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Tag(name = "demo - api")
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/demo")
public class DemoController {
    private final DemoService demoService;
    @MethodCache
    @GetMapping("/{id}")
    public String getUser(@PathVariable("id") String id) {
        return demoService.getUser(id);
    }
    @Operation(summary = "查詢用戶")
    @MethodCache
    @PostMapping("/list")
    public String findUser(@RequestBody FindUserParam param) {
        return demoService.findUser(param);
    }
}

通過(guò)反復(fù)調(diào)用被@MethodCache注解修飾的方法,會(huì)發(fā)現(xiàn)若緩存池有數(shù)據(jù),將不會(huì)進(jìn)入方法體。

SpringCache

其實(shí)SpringCache的實(shí)現(xiàn)思路和上述方法基本一致,SpringCache提供了更優(yōu)雅的編程方式,更絲滑的緩存池切換和管理,更強(qiáng)大的功能和統(tǒng)一規(guī)范。

EnableCaching

使用 @EnableCaching 開啟SpringCache功能,無(wú)需引入額外的pom。

默認(rèn)情況下,緩存池將由 ConcurrentMapCacheManager 這個(gè)對(duì)象管理,也就是默認(rèn)是程序內(nèi)存中緩存。其中用于存放緩存數(shù)據(jù)的是一個(gè) ConcurrentHashMap,源碼如下:

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
    ......
}

此外可選的緩存池管理對(duì)象還有:

  • EhCacheCacheManager
  • JCacheCacheManager
  • RedisCacheManager
  • ......

Cacheable

package com.ramble.methodcache.controller;
import com.ramble.methodcache.controller.param.FindUserParam;
import com.ramble.methodcache.service.DemoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
@Tag(name = "user - api")
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/user")
public class UserController {
    private final DemoService demoService;
    @Cacheable(value = "userCache")
    @GetMapping("/{id}")
    public String getUser(@PathVariable("id") String id) {
        return demoService.getUser(id);
    }
    @Operation(summary = "查詢用戶")
    @Cacheable(value = "userCache")
    @PostMapping("/list")
    public String findUser(@RequestBody FindUserParam param) {
        return demoService.findUser(param);
    }
}
  • 使用@Cacheable注解修飾需要緩存返回值的方法
  • value必填,不然運(yùn)行時(shí)報(bào)異常。類似一個(gè)分組,將不同的數(shù)據(jù)或者方法(當(dāng)然也可以其他維度,主要看業(yè)務(wù)需要)放到一堆,便于管理
  • 可以修飾接口方法,但是不建議,IDEA會(huì)報(bào)一個(gè)提示Spring doesn't recommend to annotate interface methods with @Cache* annotation

常用屬性:

  • value:緩存名稱
  • cacheNames:緩存名稱。value 和cacheNames都被AliasFor注解修飾,他們互為別名
  • key:緩存數(shù)據(jù)時(shí)候的key,默認(rèn)使用方法參數(shù)的值,可以使用SpEL生產(chǎn)key
  • keyGenerator:key生產(chǎn)器。和key二選一
  • cacheManager:緩存管理器
  • cacheResolver:和caheManager二選一,互為別名
  • condition:創(chuàng)建緩存的條件,可用SpEL表達(dá)式(如#id>0,表示當(dāng)入?yún)d大于0時(shí)候才緩存方法返回值)
  • unless:不創(chuàng)建緩存的條件,如#result==null,表示方法返回值為null的時(shí)候不緩存

CachePut

用來(lái)更新緩存。被CachePut注解修飾的方法,在被調(diào)用的時(shí)候不會(huì)校驗(yàn)緩存池中是否已經(jīng)存在緩存,會(huì)直接發(fā)起調(diào)用,然后將返回值放入緩存池中。

CacheEvict

用來(lái)刪除緩存,會(huì)根據(jù)key來(lái)刪除緩存中的數(shù)據(jù)。并且不會(huì)將本方法返回值緩存起來(lái)。

常用屬性:

  • value/cacheeName:緩存名稱,或者說(shuō)緩存分組
  • key:緩存數(shù)據(jù)的鍵
  • allEntries:是否根據(jù)緩存名稱清空所有緩存,默認(rèn)為false。當(dāng)此值為true的時(shí)候,將根據(jù)cacheName清空緩存池中的數(shù)據(jù),然后將新的返回值放入緩存
  • beforeInvocation:是否在方法執(zhí)行之前就清空緩存,默認(rèn)為false

Caching

此注解用于在一個(gè)方法或者類上面,同時(shí)指定多個(gè)SpringCache相關(guān)注解。這個(gè)也是SpringCache的強(qiáng)大之處,可以自定義各種緩存創(chuàng)建、更新、刪除的邏輯,應(yīng)對(duì)復(fù)雜的業(yè)務(wù)場(chǎng)景。

屬性:

  • cacheable:指定@Cacheable注解
  • put:指定@CachePut注解
  • evict:指定@CacheEvict注解

源碼:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
    Cacheable[] cacheable() default {};
    CachePut[] put() default {};
    CacheEvict[] evict() default {};
}

相當(dāng)于就是注解里面套注解,用來(lái)完成復(fù)雜和多變的場(chǎng)景,這個(gè)設(shè)計(jì)相當(dāng)?shù)耐廴?/p>

CacheConfig

放在類上面,那么類中所有方法都會(huì)被緩存

SpringCacheEnv

SpringCache內(nèi)置了一些環(huán)境變量,可用于各個(gè)注解的屬性中。

  • methodName:被修飾方法的方法名
  • method:被修飾方法的Method對(duì)象
  • target:被修飾方法所屬的類對(duì)象的實(shí)例
  • targetClass:被修飾方法所屬類對(duì)象
  • args:方法入?yún)?,是一個(gè) object[] 數(shù)組
  • caches:這個(gè)對(duì)象其實(shí)就是ConcurrentMapCacheManager中的cacheMap,這個(gè)cacheMap呢就是一開頭提到的ConcurrentHashMap,即緩存池。caches的使用場(chǎng)景尚不明了。
  • argumentName:方法的入?yún)?/li>
  • result:方法執(zhí)行的返回值

使用示例:

@Cacheable(value = "userCache", condition = "#result!=null",unless = "#result==null")
public String showEnv() { 
    return "打印各個(gè)環(huán)境變量";
 }

表示僅當(dāng)方法返回值不為null的時(shí)候才緩存結(jié)果,這里通過(guò)result env 獲取返回值。

另外,condition 和 unless 為互補(bǔ)關(guān)系,上述condition = "#result!=null"和unless = "#result==null"其實(shí)是一個(gè)意思。

@Cacheable(value = "userCache", key = "#name")
public String showEnv(String id, String name) {
    return "打印各個(gè)環(huán)境變量";
}

表示使用方法入?yún)⒆鳛樵摋l緩存數(shù)據(jù)的key,若傳入的name為gg,則實(shí)際緩存的數(shù)據(jù)為:gg->打印各個(gè)環(huán)境變量

另外,如果name為空會(huì)報(bào)異常,因?yàn)榫彺鎘ey不允許為null

@Cacheable(value = "userCache",key = "#root.args")
public String showEnv(String id, String name) {
    return "打印各個(gè)環(huán)境變量";
}

表示使用方法的入?yún)⒆鳛榫彺娴膋ey,若傳遞的參數(shù)為id=100,name=gg,則實(shí)際緩存的數(shù)據(jù)為:Object[]->打印各個(gè)環(huán)境變量,Object[]數(shù)組中包含兩個(gè)值。

既然是數(shù)組,可以通過(guò)下標(biāo)進(jìn)行訪問,root.args[1] 表示獲取第二個(gè)參數(shù),本例中即 取 name 的值 gg,則實(shí)際緩存的數(shù)據(jù)為:gg->打印各個(gè)環(huán)境變量。

@Cacheable(value = "userCache",key = "#root.targetClass")
public String showEnv(String id, String name) {
    return "打印各個(gè)環(huán)境變量";
}

表示使用被修飾的方法所屬的類作為緩存key,實(shí)際緩存的數(shù)據(jù)為:Class->打印各個(gè)環(huán)境變量,key為class對(duì)象,不是全限定名,全限定名是一個(gè)字符串,這里是class對(duì)象。

可是,不是很懂這樣設(shè)計(jì)的應(yīng)用場(chǎng)景是什么......

@Cacheable(value = "userCache",key = "#root.target")
public String showEnv(String id, String name) {
    return "打印各個(gè)環(huán)境變量";
}

表示使用被修飾方法所屬類的實(shí)例作為key,實(shí)際緩存的數(shù)據(jù)為:UserController->打印各個(gè)環(huán)境變量。

被修飾的方法就是在UserController中,調(diào)試的時(shí)候甚至可以獲取到此實(shí)例注入的其它容器對(duì)象,如userService等。

可是,不是很懂這樣設(shè)計(jì)的應(yīng)用場(chǎng)景是什么......

@Cacheable(value = "userCache",key = "#root.method")
public String showEnv(String id, String name) {
    return "打印各個(gè)環(huán)境變量";
}

表示使用Method對(duì)象作為緩存的key,是Method對(duì)象,不是字符串。

可是,不是很懂這樣設(shè)計(jì)的應(yīng)用場(chǎng)景是什么......

@Cacheable(value = "userCache",key = "#root.methodName")
public String showEnv(String id, String name) {
    return "打印各個(gè)環(huán)境變量";
}

表示使用方法名作為緩存的key,就是一個(gè)字符串。

如何獲取緩存的數(shù)據(jù)?

ConcurrentMapCacheManager的cacheMap是一個(gè)私有變量,所以沒有辦法可以打印緩存池中的數(shù)據(jù),不過(guò)可以通過(guò)調(diào)試的方式進(jìn)入對(duì)象內(nèi)部查看。如下:

@Tag(name = "user - api")
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/user")
public class UserController {
    private final ConcurrentMapCacheManager cacheManager;
    /**
     * 只有調(diào)試才課可以查看緩存池中的數(shù)據(jù)
     */
    @GetMapping("/cache")
    public void showCacheData() {
        //需要debug進(jìn)入
        Collection<String> cacheNames = cacheManager.getCacheNames();
    }
}

總結(jié)

雖然提供了很多的環(huán)境變量,但是大多都無(wú)法找到對(duì)應(yīng)的使用場(chǎng)景,其實(shí)在實(shí)際開發(fā)中,最常見的就是key的生產(chǎn),一般而言使用類名+方法名+參數(shù)值足矣。

以上就是SpringBoot緩存方法返回值的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot緩存方法返回值的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Maven dependencies與dependencyManagement的區(qū)別詳解

    Maven dependencies與dependencyManagement的區(qū)別詳解

    這篇文章主要介紹了Maven dependencies與dependencyManagement的區(qū)別詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-04-04
  • 詳解如何在spring中創(chuàng)建全局異常處理器

    詳解如何在spring中創(chuàng)建全局異常處理器

    全局異常處理器在實(shí)際項(xiàng)目開發(fā)中是一個(gè)很重要的工具,對(duì)保證代碼的正常運(yùn)行有很重要的作用,所以下面來(lái)講一下如何在spring中創(chuàng)建一個(gè)全局異常處理器,感興趣的的朋友可以參考下
    2023-12-12
  • spring boot 使用@Async實(shí)現(xiàn)異步調(diào)用方法

    spring boot 使用@Async實(shí)現(xiàn)異步調(diào)用方法

    本篇文章主要介紹了spring boot 使用@Async實(shí)現(xiàn)異步調(diào)用方法,具有一定的參考價(jià)值,有興趣的可以了解一下。
    2017-04-04
  • Spring?Cloud?Stream消息驅(qū)動(dòng)組件使用方法介紹

    Spring?Cloud?Stream消息驅(qū)動(dòng)組件使用方法介紹

    Spring?Cloud?Stream?消息驅(qū)動(dòng)組件幫助我們更快速,更方便,更友好的去構(gòu)建消息驅(qū)動(dòng)微服務(wù)的。當(dāng)時(shí)定時(shí)任務(wù)和消息驅(qū)動(dòng)的?個(gè)對(duì)比。消息驅(qū)動(dòng):基于消息機(jī)制做一些事情
    2022-09-09
  • Java8默認(rèn)方法Default Methods原理及實(shí)例詳解

    Java8默認(rèn)方法Default Methods原理及實(shí)例詳解

    這篇文章主要介紹了Java8默認(rèn)方法Default Methods原理及實(shí)例詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • java實(shí)現(xiàn)簡(jiǎn)單注冊(cè)選擇所在城市

    java實(shí)現(xiàn)簡(jiǎn)單注冊(cè)選擇所在城市

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單注冊(cè)選擇所在城市的相關(guān)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-04-04
  • 常見的排序算法,一篇就夠了

    常見的排序算法,一篇就夠了

    這篇文章主要介紹了一些常用排序算法整理,插入排序算法、直接插入排序、希爾排序、選擇排序、冒泡排序等排序,需要的朋友可以參考下
    2021-07-07
  • Mybatis實(shí)體類對(duì)象入?yún)⒉樵兊墓P記

    Mybatis實(shí)體類對(duì)象入?yún)⒉樵兊墓P記

    這篇文章主要介紹了Mybatis實(shí)體類對(duì)象入?yún)⒉樵兊墓P記,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 使用socket進(jìn)行服務(wù)端與客戶端傳文件的方法

    使用socket進(jìn)行服務(wù)端與客戶端傳文件的方法

    這篇文章主要介紹了使用socket進(jìn)行服務(wù)端與客戶端傳文件的方法,需要的朋友可以參考下
    2017-08-08
  • Java 面試題和答案 -(上)

    Java 面試題和答案 -(上)

    本文主要介紹Java 面試題和答案,這里整理了Java面試中出現(xiàn)的各種題型,和相應(yīng)知識(shí)點(diǎn),有需要的小伙伴可以好好參考下,幫助大家面試成功
    2016-09-09

最新評(píng)論