【深入理解@EnableCaching】

深入理解@EnableCaching

@EnableCaching 是 Spring Framework 中用于启用和配置缓存机制的一个注解。它通常被应用在配置类上,用来告诉 Spring 容器需要激活缓存相关的功能。Spring 的缓存抽象提供了一种简单的机制来管理缓存,可以减少重复的计算或数据库查询,从而提高应用程序的性能。

使用 @EnableCaching

当你在配置类上使用 @EnableCaching 注解时,Spring 会自动检测并注册所有与缓存相关的 Bean(例如 CacheManagerCacheResolver),并且会对带有缓存相关注解的方法进行拦截,如 @Cacheable@CachePut@CacheEvict

缓存注解

  • @Cacheable:当方法被调用时,Spring 会检查缓存中是否已经存在结果。如果存在,则直接返回缓存的结果,而不执行实际的方法;如果不存在,则执行方法,并将结果存入缓存。
  • @CachePut:无论缓存中是否存在数据,都会执行方法,并将方法的结果存入缓存。
  • @CacheEvict:用于从缓存中移除数据。可以指定在方法执行前或后移除。

自定义缓存解析器

除了基本的注解外,你还可以自定义 CacheResolver 来决定使用哪个缓存或如何解析缓存键。这可以通过实现 CacheResolver 接口或使用 SimpleCacheResolver 来完成。

配置 CacheManager

为了使缓存工作,你需要配置一个或多个 CacheManager,它们负责创建和管理缓存。Spring 支持多种类型的 CacheManager,比如基于 EhCache、Caffeine、ConcurrentMap 等的实现。你可以通过 Java 配置或者 XML 来定义 CacheManager

模式选项

@EnableCaching 注解还有一个可选的 mode 属性,它可以接受以下值:

  • proxy (默认):使用代理的方式来拦截被缓存注解标注的方法。这是最常用的模式,适用于大多数场景。

  • aspectj:使用 AspectJ 编译期织入的方式来进行拦截。这种方式可以在不改变字节码的情况下对方法进行拦截,但是需要额外的编译步骤。

使用示例

1. 添加依赖

首先,确保你的 pom.xmlbuild.gradle 文件中包含了 Spring Cache 和你选择的缓存提供者的依赖。这里我们以 Spring Boot 项目为例,使用 Maven 构建工具,并且不引入特定的第三方缓存库,而是使用 Spring 内置的 ConcurrentMapCacheManager

pom.xml 中添加:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

创建一个配置类来启用缓存并定义 CacheManager

java 复制代码
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        // 定义两个缓存:books 和 authors
        return new ConcurrentMapCacheManager("books", "authors");
    }
}

3. 使用缓存注解

接下来,我们创建一个服务类,其中包含一些被缓存注解标注的方法。

java 复制代码
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

@Service
public class BookService {

    // 模拟的数据存储
    private final Map<Long, String> bookStore = new ConcurrentHashMap<>();

    // 使用 @Cacheable 注解,尝试从 "books" 缓存中获取数据
    @Cacheable(value = "books", key = "#id")
    public String findBookById(Long id) {
        System.out.println("查询书籍: " + id);
        return bookStore.getOrDefault(id, "未知书籍");
    }

    // 使用 @CachePut 注解,更新或插入缓存中的数据
    @CachePut(value = "books", key = "#book.id")
    public String saveBook(Book book) {
        System.out.println("保存书籍: " + book);
        bookStore.put(book.getId(), book.getName());
        return book.getName();
    }

    // 使用 @CacheEvict 注解,移除缓存中的数据
    @CacheEvict(value = "books", key = "#id")
    public void deleteBookById(Long id) {
        System.out.println("删除书籍: " + id);
        bookStore.remove(id);
    }
}

// 假设有一个简单的 Book 类
class Book {
    private Long id;
    private String name;

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

4. 测试缓存功能

为了测试上面定义的缓存逻辑,你可以编写一个控制器或者直接在主应用程序中调用这些服务方法。这里我们将通过控制台输出来简单地测试一下。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class CacheTestRunner implements CommandLineRunner {

    private final BookService bookService;

    @Autowired
    public CacheTestRunner(BookService bookService) {
        this.bookService = bookService;
    }

    @Override
    public void run(String... args) throws Exception {
        // 第一次查询会打印日志并返回默认值
        System.out.println("第一次查询: " + bookService.findBookById(1L));

        // 保存书籍到缓存
        Book book = new Book();
        book.setId(1L);
        book.setName("Spring in Action");
        bookService.saveBook(book);

        // 第二次查询应该不会打印日志,因为数据已经在缓存中
        System.out.println("第二次查询: " + bookService.findBookById(1L));

        // 删除书籍并清除缓存
        bookService.deleteBookById(1L);

        // 再次查询应该会打印日志并返回默认值
        System.out.println("第三次查询: " + bookService.findBookById(1L));
    }
}

​ 当你运行这个 Spring Boot 应用程序时,你会看到控制台上输出的日志信息,这表明了缓存是如何工作的。第一次调用 findBookById 方法时,由于缓存中没有数据,它会执行实际的方法逻辑;而第二次调用时,它应该直接从缓存中获取数据,不再打印"查询书籍"的日志;最后,当删除书籍后,再次查询该书籍时,它又会执行实际的方法逻辑,因为缓存已经被清除了。

​ 这个例子展示了如何使用 @EnableCaching 启用缓存功能,并使用 @Cacheable@CachePut@CacheEvict 注解来管理缓存。当然,实际应用中可能会更加复杂,但这是一个很好的起点。

注意事项

  • @EnableCaching 只有在 Spring 上下文启动时才会生效,因此不能动态地开启或关闭缓存。
  • 缓存注解只对公共方法有效。如果你尝试在非公共方法上使用缓存注解,它们将不会起作用。
  • 在使用缓存时要考虑到缓存一致性的问题,确保缓存的数据是最新的,以避免出现脏读的情况。

总之,@EnableCaching 是 Spring 提供的一种强大而灵活的工具,可以帮助开发者轻松地添加缓存逻辑到他们的应用程序中。正确配置和使用它能够显著提升应用的响应速度和效率。

相关推荐
小_太_阳22 分钟前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾25 分钟前
scala借阅图书保存记录(三)
开发语言·后端·scala
黑胡子大叔的小屋43 分钟前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
ThisIsClark1 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
星就前端叭1 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
雷神乐乐2 小时前
Spring学习(一)——Sping-XML
java·学习·spring
小林coding2 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者2 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu
V+zmm101342 小时前
基于小程序宿舍报修系统的设计与实现ssm+论文源码调试讲解
java·小程序·毕业设计·mvc·ssm
从善若水3 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust