玩转Spring Cache --- 开启基于注解的缓存功能@EnableCaching原理了解【享学Spring】(上)

简介: 玩转Spring Cache --- 开启基于注解的缓存功能@EnableCaching原理了解【享学Spring】(上)

前言


缓存现已成为了项目的标配,更是面必问的知识点。若你说你的项目中还没有使用到缓存,估计你都不太好意思介绍你的项目。


Spring3.1之后就引入了基于注解的缓存技术,但是要明白Spring基于注解的缓存技术并不是一个具体的实现方案(EHCache、OSCache、Redis才是具体的缓存方案),而是对缓存使用的一个抽象。

基于注解的缓存能够在现有的代码基础上只需要加入少量的缓存注解(@Cacheable、@CachePut、@CacheEvict、@Caching)即能够达到缓存方法的返回结果的效果。(使用缓存注解实现缓存无需关心缓存具体的实现产品~)


关于直接调用API方式来使用缓存,可参考:

【小家Spring】聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache

开启缓存注解的步骤


通过前面多篇文章的学习我们发现,启用Spring的一个功能模块是一件非常方便的事。自然作为Spring框架的核心功能之缓存注解,该功能自然也继承了Spring这个优良特性,使它生效只需要轻松两步:


  1. 配置类上开启缓存注解支持:@EnableCaching
  2. 向容器内至少放置一个CacheManager类型的Bean


仅仅简单的两步后,就可以开工使用Spring强大的缓存注解功能了。

简单示例

按照上面两个步骤配置如下:

@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    public ConcurrentMapCacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        //cacheManager.setStoreByValue(true); //true表示缓存一份副本,否则缓存引用
        return cacheManager;
    }
}


配置好后,开工书写需要使用缓存的代码,此处以Service为例:

@Service
public class CacheDemoServiceImpl implements CacheDemoService {
    @Cacheable(cacheNames = "demoCache", key = "#id")
    @Override
    public Object getFromDB(Integer id) {
        System.out.println("模拟去db查询~~~" + id);
        return "hello cache...";
    }
}


单元测试:


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {
    @Autowired
    private CacheDemoService cacheDemoService;
    @Autowired
    private CacheManager cacheManager;
    @Test
    public void test1() {
        cacheDemoService.getFromDB(1);
        cacheDemoService.getFromDB(1);
        // 校验缓存里的内容~~~~
        Cache demoCache = cacheManager.getCache("demoCache");
        System.out.println(demoCache.getName());
        System.out.println(demoCache.get(1, String.class));
    }
}

输出:

模拟去db查询~~~1
---------------------------------
demoCache
hello cache...


从打印结果的两个可以证明缓存生效了


  1. getFromDB(1)调用了两次,但日志只输出一句,证明第二次并没有执行方法体,缓存生效
  2. 校验代码中,可以拿到名为demoCache的这个Cache对象,并且该Cache内是存在key=1这个键值对的,证明结果确实存入到缓存里了


@EnableCaching开启缓存的原理解析


@EnableCaching这注解用于开启Spring的缓存注解功能,它是一个模块注解,功能类似于xml时代的:<cache:annotation-driven>配置项。本处介绍一下这个注解具体做了哪些事~


首先来到这个注解本身:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
  boolean proxyTargetClass() default false;
  AdviceMode mode() default AdviceMode.PROXY;
  int order() default Ordered.LOWEST_PRECEDENCE;
}


该注解的这几个属性我此处略过不解释了,相信应该没有小伙伴有意见吧。同样的,此处重点关注对象为:CachingConfigurationSelector


CachingConfigurationSelector


这个类继承自AdviceModeImportSelector。关于AdviceModeImportSelector这个抽象类,熟悉我博文的小伙伴对这个类应该都不默认了,因为在讲解@EnableAsync、@EnableTransactionManagement的时候都解释过多遍,因此本文对此抽象父类不再鳌诉。

有疑惑的可参考:

【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)

【小家Spring】从基于@Transactional全注解方式的声明式事务入手,彻底掌握Spring事务管理的原理


public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
  ...
  static {
    ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
    jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
    jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
  }
  // 绝大多数情况下我们不会使用ASPECTJ,若要使用它还得额外导包~
  // getAspectJImports()这个方法略
  @Override
  public String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
      case PROXY:
        return getProxyImports();
      case ASPECTJ:
        return getAspectJImports();
      default:
        return null;
    }
  }
  // 向容器导入了AutoProxyRegistrar和ProxyCachingConfiguration
  // 若JSR107的包存在(导入了javax.cache:cache-api这个包),并且并且存在ProxyJCacheConfiguration这个类
  // 显然ProxyJCacheConfiguration这个类我们一般都不会导进来~~~~  所以JSR107是不生效的。   但是但是Spring是支持的,非常良心
  private String[] getProxyImports() {
    List<String> result = new ArrayList<>(3);
    result.add(AutoProxyRegistrar.class.getName());
    result.add(ProxyCachingConfiguration.class.getName());
    if (jsr107Present && jcacheImplPresent) {
      result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
    }
    return StringUtils.toStringArray(result);
  }
  ...
}


它完成了一件事:向容器内注入了AutoProxyRegistrarProxyCachingConfiguration这两个Bean。


AutoProxyRegistrar


关于AutoProxyRegistrar,先看图:


image.png


看图的目的是这个类它在事务章节里也被用到,而且已在那做过详述,因此本文略过。需要了解详细的请参考:【小家Spring】从基于@Transactional全注解方式的声明式事务入手,彻底掌握Spring事务管理的原理


说明:该类向Spring容器注入了一个自动代理创建器,因此可想而知缓存的代理对象,最终是委托给自动代理创建器来完成的(和@Async不同哦~)。

相关文章
|
1月前
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
48 0
|
4月前
|
存储 缓存 NoSQL
【Azure Redis 缓存】关于Azure Cache for Redis 服务在传输和存储键值对(Key/Value)的加密问题
【Azure Redis 缓存】关于Azure Cache for Redis 服务在传输和存储键值对(Key/Value)的加密问题
|
4月前
|
缓存 NoSQL Java
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
|
1月前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
83 5
|
1月前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
293 2
|
2月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
89 2
|
4月前
|
缓存 NoSQL 测试技术
【Azure Redis 缓存】Azure Redis 功能性讨论三: 调优参数配置
【Azure Redis 缓存】Azure Redis 功能性讨论三: 调优参数配置
|
4月前
|
存储 缓存 NoSQL
微服务缓存原理与最佳实践
微服务缓存原理与最佳实践
|
4月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Azure Cache for Redis 服务的导出RDB文件无法在自建的Redis服务中导入
【Azure Redis 缓存】Azure Cache for Redis 服务的导出RDB文件无法在自建的Redis服务中导入
|
4月前
|
缓存 开发框架 NoSQL
【Azure Redis 缓存】VM 里的 Redis 能直接迁移到 Azure Cache for Redis ? 需要改动代码吗?
【Azure Redis 缓存】VM 里的 Redis 能直接迁移到 Azure Cache for Redis ? 需要改动代码吗?