最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Cache擴(kuò)展功能實(shí)現(xiàn)過(guò)程解析

 更新時(shí)間:2020年02月27日 10:48:15   作者:min.jiang  
這篇文章主要介紹了Spring Cache擴(kuò)展功能實(shí)現(xiàn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

兩個(gè)需求緩存失效時(shí)間支持在方法的注解上指定

Spring Cache默認(rèn)是不支持在@Cacheable上添加過(guò)期時(shí)間的,可以在配置緩存容器時(shí)統(tǒng)一指定:

@Bean
public CacheManager cacheManager(
    @SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
  CustomizedRedisCacheManager cacheManager= new CustomizedRedisCacheManager(redisTemplate);
  cacheManager.setDefaultExpiration(60);
  Map<String,Long> expiresMap=new HashMap<>();
  expiresMap.put("Product",5L);
  cacheManager.setExpires(expiresMap);
  return cacheManager;
}

想這樣配置過(guò)期時(shí)間,焦點(diǎn)在value的格式上Product#5#2,詳情下面會(huì)詳細(xì)說(shuō)明。

@Cacheable(value = {"Product#5#2"},key ="#id")

上面兩種各有利弊,并不是說(shuō)哪一種一定要比另外一種強(qiáng),根據(jù)自己項(xiàng)目的實(shí)際情況選擇。

在緩存即將過(guò)期時(shí)主動(dòng)刷新緩存

一般緩存失效后,會(huì)有一些請(qǐng)求會(huì)打到后端的數(shù)據(jù)庫(kù)上,這段時(shí)間的訪問(wèn)性能肯定是比有緩存的情況要差很多。所以期望在緩存即將過(guò)期的某一時(shí)間點(diǎn)后臺(tái)主動(dòng)去更新緩存以確保前端請(qǐng)求的緩存命中率,示意圖如下:

Srping 4.3提供了一個(gè)sync參數(shù)。是當(dāng)緩存失效后,為了避免多個(gè)請(qǐng)求打到數(shù)據(jù)庫(kù),系統(tǒng)做了一個(gè)并發(fā)控制優(yōu)化,同時(shí)只有一個(gè)線程會(huì)去數(shù)據(jù)庫(kù)取數(shù)據(jù)其它線程會(huì)被阻塞。

背景

我以Spring Cache +Redis為前提來(lái)實(shí)現(xiàn)上面兩個(gè)需求,其它類型的緩存原理應(yīng)該是相同的。

本文內(nèi)容未在生產(chǎn)環(huán)境驗(yàn)證過(guò),也許有不妥的地方,請(qǐng)多多指出。

擴(kuò)展RedisCacheManagerCustomizedRedisCacheManager

繼承自RedisCacheManager,定義兩個(gè)輔助性的屬性:

/**
   * 緩存參數(shù)的分隔符
   * 數(shù)組元素0=緩存的名稱
   * 數(shù)組元素1=緩存過(guò)期時(shí)間TTL
   * 數(shù)組元素2=緩存在多少秒開始主動(dòng)失效來(lái)強(qiáng)制刷新
   */
  private String separator = "#";

  /**
   * 緩存主動(dòng)在失效前強(qiáng)制刷新緩存的時(shí)間
   * 單位:秒
   */
  private long preloadSecondTime=0;

注解配置失效時(shí)間簡(jiǎn)單的方法就是在容器名稱上動(dòng)動(dòng)手腳,通過(guò)解析特定格式的名稱來(lái)變向?qū)崿F(xiàn)失效時(shí)間的獲取。比如第一個(gè)#后面的5可以定義為失效時(shí)間,第二個(gè)#后面的2是刷新緩存的時(shí)間,只需要重寫getCache:

  • 解析配置的value值,分別計(jì)算出真正的緩存名稱,失效時(shí)間以及緩存刷新的時(shí)間
  • 調(diào)用構(gòu)造函數(shù)返回緩存對(duì)象
@Override
public Cache getCache(String name) {

  String[] cacheParams=name.split(this.getSeparator());
  String cacheName = cacheParams[0];

  if(StringUtils.isBlank(cacheName)){
    return null;
  }

  Long expirationSecondTime = this.computeExpiration(cacheName);

  if(cacheParams.length>1) {
    expirationSecondTime=Long.parseLong(cacheParams[1]);
    this.setDefaultExpiration(expirationSecondTime);
  }
  if(cacheParams.length>2) {
    this.setPreloadSecondTime(Long.parseLong(cacheParams[2]));
  }

  Cache cache = super.getCache(cacheName);
  if(null==cache){
    return cache;
  }
  logger.info("expirationSecondTime:"+expirationSecondTime);
  CustomizedRedisCache redisCache= new CustomizedRedisCache(
      cacheName,
      (this.isUsePrefix() ? this.getCachePrefix().prefix(cacheName) : null),
      this.getRedisOperations(),
      expirationSecondTime,
      preloadSecondTime);
  return redisCache;

}

CustomizedRedisCache

主要是實(shí)現(xiàn)緩存即將過(guò)期時(shí)能夠主動(dòng)觸發(fā)緩存更新,核心是下面這個(gè)get方法。在獲取到緩存后再次取緩存剩余的時(shí)間,如果時(shí)間小余我們配置的刷新時(shí)間就手動(dòng)刷新緩存。為了不影響get的性能,啟用后臺(tái)線程去完成緩存的刷新。

public ValueWrapper get(Object key) {

  ValueWrapper valueWrapper= super.get(key);
  if(null!=valueWrapper){
    Long ttl= this.redisOperations.getExpire(key);
    if(null!=ttl&& ttl<=this.preloadSecondTime){
      logger.info("key:{} ttl:{} preloadSecondTime:{}",key,ttl,preloadSecondTime);
      ThreadTaskHelper.run(new Runnable() {
        @Override
        public void run() {
          //重新加載數(shù)據(jù)
          logger.info("refresh key:{}",key);
CustomizedRedisCache.this.getCacheSupport().refreshCacheByKey(CustomizedRedisCache.super.getName(),key.toString());
        }
      });

    }
  }
  return valueWrapper;
}

ThreadTaskHelper是個(gè)幫助類,但需要考慮重復(fù)請(qǐng)求問(wèn)題,及相同的數(shù)據(jù)在并發(fā)過(guò)程中只允許刷新一次,這塊還沒(méi)有完善就不貼代碼了。

攔截@Cacheable,并記錄執(zhí)行方法信息

上面提到的緩存獲取時(shí),會(huì)根據(jù)配置的刷新時(shí)間來(lái)判斷是否需要刷新數(shù)據(jù),當(dāng)符合條件時(shí)會(huì)觸發(fā)數(shù)據(jù)刷新。但它需要知道執(zhí)行什么方法以及更新哪些數(shù)據(jù),所以就有了下面這些類。

CacheSupport

刷新緩存接口,可刷新整個(gè)容器的緩存也可以只刷新指定鍵的緩存。

public interface CacheSupport {

	/**
	 * 刷新容器中所有值
	 * @param cacheName
   */
	void refreshCache(String cacheName);

	/**
	 * 按容器以及指定鍵更新緩存
	 * @param cacheName
	 * @param cacheKey
   */
	void refreshCacheByKey(String cacheName,String cacheKey);

}

InvocationRegistry

執(zhí)行方法注冊(cè)接口,能夠在適當(dāng)?shù)牡胤街鲃?dòng)調(diào)用方法執(zhí)行來(lái)完成緩存的更新。

public interface InvocationRegistry {

	void registerInvocation(Object invokedBean, Method invokedMethod, Object[] invocationArguments, Set<String> cacheNames);

}

CachedInvocation

執(zhí)行方法信息類,這個(gè)比較簡(jiǎn)單,就是滿足方法執(zhí)行的所有信息即可。

public final class CachedInvocation {

  private Object key;
  private final Object targetBean;
  private final Method targetMethod;
  private Object[] arguments;

  public CachedInvocation(Object key, Object targetBean, Method targetMethod, Object[] arguments) {
    this.key = key;
    this.targetBean = targetBean;
    this.targetMethod = targetMethod;
    if (arguments != null && arguments.length != 0) {
      this.arguments = Arrays.copyOf(arguments, arguments.length);
    }
  }

}

CacheSupportImpl

這個(gè)類主要實(shí)現(xiàn)上面定義的緩存刷新接口以及執(zhí)行方法注冊(cè)接口

刷新緩存

獲取cacheManager用來(lái)操作緩存:

@Autowired
private CacheManager cacheManager;

實(shí)現(xiàn)緩存刷新接口方法:

@Override
public void refreshCache(String cacheName) {
	this.refreshCacheByKey(cacheName,null);
}

@Override
public void refreshCacheByKey(String cacheName, String cacheKey) {
	if (cacheToInvocationsMap.get(cacheName) != null) {
		for (final CachedInvocation invocation : cacheToInvocationsMap.get(cacheName)) {
			if(!StringUtils.isBlank(cacheKey)&&invocation.getKey().toString().equals(cacheKey)) {
				refreshCache(invocation, cacheName);
			}
		}
	}
}

反射來(lái)調(diào)用方法:

private Object invoke(CachedInvocation invocation)
			throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
	final MethodInvoker invoker = new MethodInvoker();
	invoker.setTargetObject(invocation.getTargetBean());
	invoker.setArguments(invocation.getArguments());
	invoker.setTargetMethod(invocation.getTargetMethod().getName());
	invoker.prepare();
	return invoker.invoke();
}

緩存刷新最后實(shí)際執(zhí)行是這個(gè)方法,通過(guò)invoke函數(shù)獲取到最新的數(shù)據(jù),然后通過(guò)cacheManager來(lái)完成緩存的更新操作。

private void refreshCache(CachedInvocation invocation, String cacheName) {

	boolean invocationSuccess;
	Object computed = null;
	try {
		computed = invoke(invocation);
		invocationSuccess = true;
	} catch (Exception ex) {
		invocationSuccess = false;
	}
	if (invocationSuccess) {
		if (cacheToInvocationsMap.get(cacheName) != null) {
			cacheManager.getCache(cacheName).put(invocation.getKey(), computed);
		}
	}
}

執(zhí)行方法信息注冊(cè)

定義一個(gè)Map用來(lái)存儲(chǔ)執(zhí)行方法的信息:

private Map<String, Set<CachedInvocation>> cacheToInvocationsMap;

實(shí)現(xiàn)執(zhí)行方法信息接口,構(gòu)造執(zhí)行方法對(duì)象然后存儲(chǔ)到Map中。

@Override
public void registerInvocation(Object targetBean, Method targetMethod, Object[] arguments, Set<String> annotatedCacheNames) {

	StringBuilder sb = new StringBuilder();
	for (Object obj : arguments) {
		sb.append(obj.toString());
	}

	Object key = sb.toString();

	final CachedInvocation invocation = new CachedInvocation(key, targetBean, targetMethod, arguments);
	for (final String cacheName : annotatedCacheNames) {
		String[] cacheParams=cacheName.split("#");
		String realCacheName = cacheParams[0];
		if(!cacheToInvocationsMap.containsKey(realCacheName)) {
			this.initialize();
		}
		cacheToInvocationsMap.get(realCacheName).add(invocation);
	}
}

CachingAnnotationsAspect

攔截@Cacheable方法信息并完成注冊(cè),將使用了緩存的方法的執(zhí)行信息存儲(chǔ)到Map中,key是緩存容器的名稱,value是不同參數(shù)的方法執(zhí)行實(shí)例,核心方法就是registerInvocation。

@Around("pointcut()")
public Object registerInvocation(ProceedingJoinPoint joinPoint) throws Throwable{

	Method method = this.getSpecificmethod(joinPoint);

	List<Cacheable> annotations=this.getMethodAnnotations(method,Cacheable.class);

	Set<String> cacheSet = new HashSet<String>();
	for (Cacheable cacheables : annotations) {
		cacheSet.addAll(Arrays.asList(cacheables.value()));
	}
	cacheRefreshSupport.registerInvocation(joinPoint.getTarget(), method, joinPoint.getArgs(), cacheSet);
	return joinPoint.proceed();
}

客戶端調(diào)用

指定5秒后過(guò)期,并且在緩存存活3秒后如果請(qǐng)求命中,會(huì)在后臺(tái)啟動(dòng)線程重新從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)來(lái)完成緩存的更新。理論上前端不會(huì)存在緩存不命中的情況,當(dāng)然如果正好最后兩秒沒(méi)有請(qǐng)求那也會(huì)出現(xiàn)緩存失效的情況。

@Cacheable(value = {"Product#5#2"},key ="#id")
public Product getById(Long id) {
  //...
}

代碼

可以從項(xiàng)目中下載。

引用

刷新緩存的思路取自于這個(gè)開源項(xiàng)目。https://github.com/yantrashala/spring-cache-self-refresh

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 深入淺析Java 循環(huán)中標(biāo)簽的作用

    深入淺析Java 循環(huán)中標(biāo)簽的作用

    這篇文章主要介紹了深入淺析Java 循環(huán)中標(biāo)簽的作用的相關(guān)資料,需要的朋友可以參考下
    2016-02-02
  • Spring工作原理簡(jiǎn)單探索

    Spring工作原理簡(jiǎn)單探索

    這篇文章主要介紹了Spring工作原理簡(jiǎn)單探索,涉及Springaop與IOC,動(dòng)態(tài)代理靜態(tài)代理,反射等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • Java?SpringBoot整合shiro-spring-boot-starterqi項(xiàng)目報(bào)錯(cuò)解決

    Java?SpringBoot整合shiro-spring-boot-starterqi項(xiàng)目報(bào)錯(cuò)解決

    這篇文章主要介紹了Java?SpringBoot整合shiro-spring-boot-starterqi項(xiàng)目報(bào)錯(cuò)解決,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考一下
    2022-08-08
  • 剖析Java中HashMap數(shù)據(jù)結(jié)構(gòu)的源碼及其性能優(yōu)化

    剖析Java中HashMap數(shù)據(jù)結(jié)構(gòu)的源碼及其性能優(yōu)化

    這篇文章主要介紹了Java中HashMap數(shù)據(jù)結(jié)構(gòu)的源碼及其性能優(yōu)化,文中以Java 8后HashMap的性能提升來(lái)討論了HashMap的一些優(yōu)化點(diǎn),需要的朋友可以參考下
    2016-05-05
  • Maven引入外部jar的幾種方法(小結(jié))

    Maven引入外部jar的幾種方法(小結(jié))

    這篇文章主要介紹了Maven引入外部jar的幾種方法(小結(jié)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • CentOS安裝solr 4.10.3詳細(xì)教程

    CentOS安裝solr 4.10.3詳細(xì)教程

    這篇文章主要為大家詳細(xì)介紹了CentOS安裝solr 4.10.3的詳細(xì)教程 ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • idea2017建立jsp工程及tomcat配置教程

    idea2017建立jsp工程及tomcat配置教程

    本文通過(guò)圖文并茂的形式給大家介紹了idea2017建立jsp工程及tomcat等配置的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • 一文徹底搞懂Java日期時(shí)間類詳解

    一文徹底搞懂Java日期時(shí)間類詳解

    這篇文章主要給大家介紹了關(guān)于Java日期時(shí)間類的相關(guān)資料,Calendar類的功能要比Date類強(qiáng)大很多,可以方便的進(jìn)行日期的計(jì)算,獲取日期中的信息時(shí)考慮了時(shí)區(qū)等問(wèn)題,需要的朋友可以參考下
    2023-10-10
  • java音頻播放示例分享(java如何播放音頻)

    java音頻播放示例分享(java如何播放音頻)

    java如何播放音頻?下面的代碼就介紹了java音頻播放示例,需要的朋友可以參考下
    2014-04-04
  • Java深入講解異常處理try?catch的使用

    Java深入講解異常處理try?catch的使用

    這篇文章主要介紹了Java異常處理機(jī)制try?catch流程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2022-06-06

最新評(píng)論

新干县| 广安市| 城固县| 南靖县| 博乐市| 枣阳市| 怀化市| 济宁市| 会宁县| 扎兰屯市| 沙雅县| 正蓝旗| 武城县| 贵州省| 永川市| 高邮市| 元朗区| 高安市| 合山市| 蕲春县| 巴里| 金阳县| 三明市| 安多县| 玉山县| 西吉县| 泗洪县| 汝城县| 镇安县| 余江县| 大埔区| 积石山| 炎陵县| 博乐市| 股票| 义乌市| 图木舒克市| 西充县| 柏乡县| 石狮市| 上饶市|