玩转Spring Cache --- @Cacheable/@CachePut/@CacheEvict缓存注解相关基础类打点【享学Spring】(下)

简介: 玩转Spring Cache --- @Cacheable/@CachePut/@CacheEvict缓存注解相关基础类打点【享学Spring】(下)

AbstractCacheResolver

具体实现根据调用上下文提供缓存名称集合。


// @since 4.1  实现了InitializingBean
public abstract class AbstractCacheResolver implements CacheResolver, InitializingBean {
  // 课件它还是依赖于CacheManager的
  @Nullable
  private CacheManager cacheManager;
  // 构造函数统一protected
  protected AbstractCacheResolver() {
  }
  protected AbstractCacheResolver(CacheManager cacheManager) {
    this.cacheManager = cacheManager;
  }
  // 设置,指定一个CacheManager
  public void setCacheManager(CacheManager cacheManager) {
    this.cacheManager = cacheManager;
  }
  public CacheManager getCacheManager() {
    Assert.state(this.cacheManager != null, "No CacheManager set");
    return this.cacheManager;
  }
  // 做了一步校验而已~~~CacheManager 必须存在
  // 这是一个使用技巧哦   自己的在设计框架的框架的时候可以使用~
  @Override
  public void afterPropertiesSet()  {
    Assert.notNull(this.cacheManager, "CacheManager is required");
  }
  @Override
  public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
    // getCacheNames抽象方法,子类去实现~~~~
    Collection<String> cacheNames = getCacheNames(context);
    if (cacheNames == null) {
      return Collections.emptyList();
    }
    // 根据cacheNames  去CacheManager里面拿到Cache对象, 作为最终的返回
    Collection<Cache> result = new ArrayList<>(cacheNames.size());
    for (String cacheName : cacheNames) {
      Cache cache = getCacheManager().getCache(cacheName);
      if (cache == null) {
        throw new IllegalArgumentException("Cannot find cache named '" +
            cacheName + "' for " + context.getOperation());
      }
      result.add(cache);
    }
    return result;
  }
  // 子类需要实现此抽象方法
  @Nullable
  protected abstract Collection<String> getCacheNames(CacheOperationInvocationContext<?> context);
}


此抽象类一样的,只是模版实现了resolveCaches()方法。抽象方法的实现交给了实现类

SimpleCacheResolver


public class SimpleCacheResolver extends AbstractCacheResolver {
  ...
  @Override
  protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
    return context.getOperation().getCacheNames();
  }
  ... 
}


这个实现太简单了,没啥好说的。

NamedCacheResolver

public class NamedCacheResolver extends AbstractCacheResolver {
  @Nullable
  private Collection<String> cacheNames;
  public NamedCacheResolver() {
  }
  public NamedCacheResolver(CacheManager cacheManager, String... cacheNames) {
    super(cacheManager);
    this.cacheNames = new ArrayList<>(Arrays.asList(cacheNames));
  }
  /**
   * Set the cache name(s) that this resolver should use.
   */
  public void setCacheNames(Collection<String> cacheNames) {
    this.cacheNames = cacheNames;
  }
  @Override
  protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
    return this.cacheNames;
  }
}



此解析器的特点是,可以自己setCacheNames()。


若内建的不符合条件,我们可以自己实现一个自己的CacheResolver。比如实现和业务相关的缓存处理器(若Class==某Class,做些特殊的操作之类的)


需要注意的是:即使你配置使用的是CacheResolver,你也必须在配置里提供cacheNames至少一个的,因为毕竟是根据你配置的CacheNames去CacheManager里找(当然,若是你的自定义实现除外)


CacheOperationSourcePointcut


Pointcut小伙伴应该不陌生,在AOP章节重点又重点的描述过,我们知道Pointcut直接关乎到是否要生成代理对象,所以此类还是蛮重要的。

// @since 3.1 它是个StaticMethodMatcherPointcut  所以方法入参它不关心
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
  // 如果你这个类就是一个CacheManager,不切入
  @Override
  public boolean matches(Method method, Class<?> targetClass) {
    if (CacheManager.class.isAssignableFrom(targetClass)) {
      return false;
    }
    // 获取到当前的缓存属性源~~~getCacheOperationSource()是个抽象方法
    CacheOperationSource cas = getCacheOperationSource();
    // 下面一句话解释为:如果方法/类上标注有缓存相关的注解,就切入进取~~
    // 具体逻辑请参见方法:cas.getCacheOperations();
    return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
  }
  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (!(other instanceof CacheOperationSourcePointcut)) {
      return false;
    }
    CacheOperationSourcePointcut otherPc = (CacheOperationSourcePointcut) other;
    return ObjectUtils.nullSafeEquals(getCacheOperationSource(), otherPc.getCacheOperationSource());
  }
  @Override
  public int hashCode() {
    return CacheOperationSourcePointcut.class.hashCode();
  }
  @Override
  public String toString() {
    return getClass().getName() + ": " + getCacheOperationSource();
  }
  /**
   * Obtain the underlying {@link CacheOperationSource} (may be {@code null}).
   * To be implemented by subclasses.
   */
  @Nullable
  protected abstract CacheOperationSource getCacheOperationSource();
}


CacheErrorHandler


处理缓存发生错误时的策略接口。在大多数情况下,提供者抛出的任何异常都应该简单地被抛出到客户机上,但是在某些情况下,基础结构可能需要以不同的方式处理缓存提供者异常。这个时候可以使用此接口来处理

接口内容十分之简单:

public interface CacheErrorHandler {
  void handleCacheGetError(RuntimeException exception, Cache cache, Object key);
  void handleCachePutError(RuntimeException exception, Cache cache, Object key, @Nullable Object value);
  void handleCacheEvictError(RuntimeException exception, Cache cache, Object key);
  void handleCacheClearError(RuntimeException exception, Cache cache);  
}


pring对它唯一内建实现为SimpleCacheErrorHandler,代码我不贴了,全是空实现,所以它是一个Adapter适配器形式的存在。

若你想自己提供CacheErrorHandler,你可以继承自SimpleCacheErrorHandler来弄~~~


AbstractCacheInvoker

它的作用是在进行缓存操作时发生异常时,调用组件CacheErrorHandler来进行处理~

// @since 4.1
public abstract class AbstractCacheInvoker {
  //protected属性~
  protected SingletonSupplier<CacheErrorHandler> errorHandler;
  protected AbstractCacheInvoker() {
    this.errorHandler = SingletonSupplier.of(SimpleCacheErrorHandler::new);
  }
  protected AbstractCacheInvoker(CacheErrorHandler errorHandler) {
    this.errorHandler = SingletonSupplier.of(errorHandler);
  }
  // 此处用的obtain方法  它能保证不为null
  public CacheErrorHandler getErrorHandler() {
    return this.errorHandler.obtain();
  }
  @Nullable
  protected Cache.ValueWrapper doGet(Cache cache, Object key) {
    try {
      return cache.get(key);
    } catch (RuntimeException ex) {
      getErrorHandler().handleCacheGetError(ex, cache, key);
      // 只有它有返回值哦  返回null
      return null;  // If the exception is handled, return a cache miss
    }
  }
  ... // 省略doPut、doEvict、doClear  处理方式同上
}



可见这个类在Spring4.1提出,专门用于处理异常的,毕竟CacheErrorHandler也是Spring4.1后才有。


总结


本篇文章为讲解缓存注解的深入原理分析进行铺垫,所以密切关注这篇文章:

【小家Spring】玩转Spring Cache — @Cacheable/@CachePut/@CacheEvict注解的使用以及原理深度剖析

相关文章
|
28天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
170 73
|
23天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
50 21
|
28天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
28天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
1月前
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
175 85
|
3月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
95 6
|
8天前
|
存储 缓存 NoSQL
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
|
8天前
|
缓存 NoSQL 关系型数据库
云端问道21期实操教学-应对高并发,利用云数据库 Tair(兼容 Redis®)缓存实现极速响应
本文介绍了如何通过云端问道21期实操教学,利用云数据库 Tair(兼容 Redis®)缓存实现高并发场景下的极速响应。主要内容分为四部分:方案概览、部署准备、一键部署和完成及清理。方案概览中,展示了如何使用 Redis 提升业务性能,降低响应时间;部署准备介绍了账号注册与充值步骤;一键部署详细讲解了创建 ECS、RDS 和 Redis 实例的过程;最后,通过对比测试验证了 Redis 缓存的有效性,并指导用户清理资源以避免额外费用。
|
30天前
|
缓存 监控 NoSQL
Redis经典问题:缓存穿透
本文详细探讨了分布式系统和缓存应用中的经典问题——缓存穿透。缓存穿透是指用户请求的数据在缓存和数据库中都不存在,导致大量请求直接落到数据库上,可能引发数据库崩溃或性能下降。文章介绍了几种有效的解决方案,包括接口层增加校验、缓存空值、使用布隆过滤器、优化数据库查询以及加强监控报警机制。通过这些方法,可以有效缓解缓存穿透对系统的影响,提升系统的稳定性和性能。
|
2月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题