玩转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不同哦~)。

相关文章
|
14天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
54 2
|
1天前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
8 0
|
1月前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
1月前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
42 0
|
1月前
|
负载均衡 Java API
Spring Cloud原理详解
Spring Cloud原理详解
67 0
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
72 6
|
4天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
6天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构

热门文章

最新文章