百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Spring的缓存帝国,得益于这 5个注解!

csdh11 2025-03-07 17:53 2 浏览

在微服务,分布式的大环境下,缓存绝对是提升系统性能的关键手段,Spring作为 Java生态中最流行的企业级应用框架,它是如何实现缓存的呢?这篇文章,我们将深入探讨 Spring中 5个核心的缓存注解。

1. 什么是缓存?

缓存(Cache)是一种存储机制,旨在临时存储数据副本,以便快速访问。缓存一般位于应用程序与数据源(如数据库)之间,能够显著降低数据访问延迟和减轻数据源的压力。

2. 缓存的类型

缓存一般可以分为下面 4种类型:

  • 本地缓存:存在于应用程序本地内存中,例如使用ConcurrentHashMap、Guava Cache等。
  • 分布式缓存:跨多个应用实例共享的缓存,例如Redis、Memcached、EhCache的分布式配置等。
  • 持久化缓存:将缓存数据持久化到磁盘,以应对应用重启后的数据恢复。
  • 非持久化缓存:缓存数据存储于内存,应用重启后数据丢失。

3. Spring缓存

Spring 从4.0版本起开始引入了 Cache模块,并提供了一套统一的缓存API,隐藏了底层缓存实现的复杂性。开发者只需通过配置和注解即可实现缓存功能,支持多种缓存实现,如EhCache、Redis、Caffeine等。

Spring缓存模块的核心组件包括:

  • CacheManager:管理多个Cache实例,根据需要选择合适的Cache。
  • Cache:具体的缓存操作接口,定义了基本的缓存操作方法,如get、put、evict等。
  • CacheResolver:根据方法信息动态解析需要使用的Cache。
  • KeyGenerator:生成缓存键的策略。

通过合理配置和使用,Spring缓存抽象能够灵活地满足各种应用场景的需求。

4. Spring缓存注解详解

Spring缓存注解主要有以下 5个:

  • @Cacheable
  • @CachePut
  • @CacheEvict
  • @Caching
  • @CacheConfig

下面我们将逐一对这些注解进行分析。

4.1 @Cacheable

@Cacheable注解用于方法级别,表示方法执行的结果可以被缓存。当方法被调用时,Spring会先检查缓存中是否存在对应的键值对,如果存在,则直接返回缓存中的结果;如果不存在,则执行方法,并将结果存入缓存。

使用示例:

@Service
publicclass UserService {

    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        // 模拟数据库访问
        simulateSlowService();
        returnnew User(id, "John Doe");
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            thrownew IllegalStateException(e);
        }
    }
}

在上述示例中,getUserById方法被@Cacheable注解修饰,指定使用users缓存,并以方法参数id作为缓存键。首次调用该方法时,缓存中不存在对应的用户信息,方法会被执行并将结果存入缓存。后续相同的调用将直接从缓存中获取结果,避免了重复的业务逻辑执行。

关键属性:

  • value / cacheNames:指定缓存的名称,可以有多个,表示多个缓存同时生效。
  • key:指定缓存的键,支持SpEL表达式,默认基于方法参数生成。
  • condition:缓存条件,符合条件的情况下才进行缓存。
  • unless:否决缓存条件,符合条件的情况下不缓存。
  • keyGenerator:自定义键生成策略。
  • cacheManager:指定使用的缓存管理器。
  • cacheResolver:指定缓存解析器。

4.2 @CachePut

@CachePut注解同样用于方法级别,但与@Cacheable不同,它总是执行方法,并将结果存入缓存。@CachePut适用于需要更新缓存但不影响方法执行结果的场景。

使用示例:

@Service
public class UserService {

    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        // 模拟更新数据库
        return user;
    }
}

在上述示例中,updateUser方法被@CachePut注解修饰,每次调用该方法时,都会执行方法逻辑(更新操作),并将返回的User对象更新到users缓存中。这样可以确保缓存中的数据与数据库中的数据保持一致。

关键属性:

@Cacheable相同,@CachePut也支持valuecacheNameskey等属性,用于指定缓存名称、键及其他配置。

4.3 @CacheEvict

@CacheEvict注解用于方法级别,表示在方法执行后,清除指定缓存中的一个或多个条目。它常用于删除操作,以确保缓存中的数据与数据源保持一致。

使用示例:

@Service
public class UserService {

    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        // 模拟删除数据库
    }
}

在上述示例中,deleteUser方法被@CacheEvict注解修饰,指定从users缓存中移除键为id的条目。这样,在用户被删除后,相应的缓存数据也被清除,防止缓存中的数据不一致。

关键属性:

  • value / cacheNames:指定缓存的名称。
  • key:指定要清除的缓存键。
  • allEntries:指定是否清除缓存中的所有条目,默认为false。
  • beforeInvocation:指定清除缓存的时机,默认为方法执行成功后。
  • cacheManager:指定使用的缓存管理器。
  • cacheResolver:指定缓存解析器。

4.4 @Caching

@Caching注解用于组合多个缓存注解,使得在一个方法上可以执行多个缓存操作。它适用于需要同时执行多个缓存行为的复杂场景。

使用示例:

@Service
public class UserService {

    @Caching(
        put = { @CachePut(value = "users", key = "#user.id"),
                @CachePut(value = "username", key = "#user.username") },
        evict = { @CacheEvict(value = "userCache", allEntries = true) }
    )
    public User addUser(User user) {
        // 模拟添加用户到数据库
        return user;
    }
}

在上述示例中,addUser方法通过@Caching注解同时执行了两个@CachePut操作,将用户信息存入不同的缓存中,并且执行了一个@CacheEvict操作,清除userCache中的所有条目。

关键属性:

@Caching主要包含以下属性:

  • cacheable:@Cacheable注解数组。
  • put:@CachePut注解数组。
  • evict:@CacheEvict注解数组。

通过组合不同类型的缓存注解,@Caching提供了更灵活的缓存操作能力。

4.5 @CacheConfig

@CacheConfig注解用于类级别,为该类中的所有缓存注解提供公共配置。例如,可以指定统一的缓存名称、缓存管理器等,减少重复配置的工作量。

使用示例:

@Service
@CacheConfig(cacheNames = "users", cacheManager = "cacheManager")
publicclass UserService {

    @Cacheable(key = "#id")
    public User getUserById(Long id) {
        // 模拟数据库访问
        returnnew User(id, "John Doe");
    }

    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        // 模拟更新数据库
        return user;
    }

    @CacheEvict(key = "#id")
    public void deleteUser(Long id) {
        // 模拟删除数据库
    }
}

在上述示例中,@CacheConfig注解指定了默认的缓存名称和缓存管理器,使得类中的所有缓存注解无需重复指定这些属性,只需关注特定的键或其他配置。

关键属性:

  • cacheNames / value:指定默认的缓存名称。
  • cacheManager:指定默认的缓存管理器。
  • cacheResolver:指定默认的缓存解析器。
  • keyGenerator:指定默认的键生成策略。

@CacheConfig通过提供类级别的缓存配置,简化了属性的配置和维护,提高了代码的可读性和可维护性。

5. 缓存框架

要使 Spring的缓存注解生效,必须配置一个缓存管理器(CacheManager)和相应的缓存提供者。Spring支持多种缓存实现,常见的包括 EhCache、Redis、Caffeine等。下面,我们介绍这 3种常用缓存提供者的配置方法。

5.1 EhCache

EhCache是一款常用的开源缓存库,支持本地内存和磁盘存储,配置灵活,适用于单机应用。

依赖配置(Maven)


    org.springframework.boot
    spring-boot-starter-cache


    net.sf.ehcache
    ehcache
    2.10.6

配置示例

创建一个EhCache配置文件ehcache.xml


    
    
    

Spring配置

@Configuration
@EnableCaching
publicclass CacheConfig {

    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        factoryBean.setShared(true);
        return factoryBean;
    }

    @Bean
    public CacheManager cacheManager(EhCacheManagerFactoryBean factoryBean) {
        returnnew EhCacheCacheManager(factoryBean.getObject());
    }
}

5.2 Redis

Redis是一种高性能的NoSQL缓存数据库,支持分布式部署,适用于大规模应用场景。

依赖配置(Maven)


    org.springframework.boot
    spring-boot-starter-cache


    org.springframework.boot
    spring-boot-starter-data-redis

配置示例(application.properties)

spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=yourpassword

Spring配置

Spring Boot会自动配置RedisCacheManager,无需额外配置。如果需要自定义配置,可以如下:

@Configuration
@EnableCaching
publicclass RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 配置默认缓存过期时间等
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(60))
            .disableCachingNullValues();
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(config)
            .build();
    }
}

5.3 Caffeine

Caffeine是一个高性能的本地缓存库,具有丰富的缓存策略和高并发性能。

依赖配置(Maven)


    org.springframework.boot
    spring-boot-starter-cache


    com.github.ben-manes.caffeine
    caffeine
    3.1.6

Spring配置

@Configuration
@EnableCaching
publicclass CaffeineCacheConfig {

    @Bean
    public Caffeine caffeineConfig() {
        return Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .maximumSize(1000);
    }

    @Bean
    public CacheManager cacheManager(Caffeine caffeine) {
        CaffeineCacheManager manager = new CaffeineCacheManager("users");
        manager.setCaffeine(caffeine);
        return manager;
    }
}

6. 案例分析

下面我们通过一个简单的 CRUD应用,演示如何在 Spring Boot项目中集成和使用缓存注解。

6.1 项目介绍

构建一个用户管理系统,包含用户的增删改查功能。通过缓存优化其中的读取操作,以提升系统性能。

6.2 环境搭建

技术栈

  • Spring Boot:快速构建项目基础。
  • Spring Data JPA:数据访问层。
  • H2数据库:内存数据库,方便演示。
  • Spring Cache:缓存抽象。
  • EhCache:作为缓存实现。

依赖配置(Maven)


    
    
        org.springframework.boot
        spring-boot-starter-web
    

    
    
        org.springframework.boot
        spring-boot-starter-data-jpa
    

    
    
        com.h2database
        h2
        runtime
    

    
    
        org.springframework.boot
        spring-boot-starter-cache
    

    
    
        net.sf.ehcache
        ehcache
        2.10.6
    

    
    
        org.projectlombok
        lombok
        provided
    

6.3 缓存配置

创建ehcache.xml文件


    
    
    

配置类

@Configuration
@EnableCaching
publicclass CacheConfig {

    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        factoryBean.setShared(true);
        return factoryBean;
    }

    @Bean
    public CacheManager cacheManager(net.sf.ehcache.CacheManager cm) {
        returnnew EhCacheCacheManager(cm);
    }
}

6.4 实体和仓库

用户实体类

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    private Long id;
    private String username;
}

用户仓库接口

public interface UserRepository extends JpaRepository {
    User findByUsername(String username);
}

6.5 服务层与缓存注解应用

@Service
@CacheConfig(cacheNames = "users")
publicclass UserService {

    @Autowired
    private UserRepository userRepository;

    @Cacheable(key = "#id")
    public User getUserById(Long id) {
        simulateSlowService();
        return userRepository.findById(id).orElse(null);
    }

    @Cacheable(key = "#username")
    public User getUserByUsername(String username) {
        simulateSlowService();
        return userRepository.findByUsername(username);
    }

    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }

    @CacheEvict(key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(2000L); // 模拟耗时操作
        } catch (InterruptedException e) {
            thrownew IllegalStateException(e);
        }
    }
}

在上述示例中:

  • getUserById和getUserByUsername方法被@Cacheable注解修饰,表示查询用户时会先从缓存中查找,若缓存不存在则执行数据库查询并将结果缓存在users缓存中。
  • updateUser方法被@CachePut注解修饰,表示更新用户信息时,会将更新后的用户对象写入缓存。
  • deleteUser方法被@CacheEvict注解修饰,表示删除用户时,会从缓存中移除对应的用户信息。

6.6 控制层

@RestController
@RequestMapping("/api/users")
publicclass UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }

    @GetMapping("/username/{username}")
    public ResponseEntity getUserByUsername(@PathVariable String username) {
        User user = userService.getUserByUsername(username);
        return ResponseEntity.ok(user);
    }

    @PostMapping
    public ResponseEntity addUser(@RequestBody User user) {
        User savedUser = userService.updateUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

6.7 测试缓存效果

  1. 启动应用程序
  2. 调用GET /api/users/{id}接口
  3. 首次调用会触发数据库查询并缓存结果。
  4. 第二次调用相同的接口,将直接从缓存中获取用户信息,响应速度更快。
  5. 调用POST /api/users接口更新用户
  6. 更新操作会通过@CachePut注解将新的用户信息更新到缓存中。
  7. 调用DELETE /api/users/{id}接口删除用户
  8. 删除操作会通过@CacheEvict注解从缓存中移除用户信息。

通过上述步骤,可以验证缓存的实际效果,发现读取操作的响应时间明显降低。

7. 增强功能

7.1 自定义缓存键生成策略

默认情况下,Spring根据方法的参数生成缓存键。对于复杂的业务场景,可能需要自定义缓存键生成策略。

自定义KeyGenerator

@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return method.getName() + "_" + Arrays.stream(params)
                .map(Object::toString)
                .collect(Collectors.joining("_"));
    }
}

使用自定义KeyGenerator

@Cacheable(cacheNames = "users", keyGenerator = "customKeyGenerator")
public User getUser(Long id, String type) {
    // 方法实现
}

7.2 缓存条件与排除

通过conditionunless属性,可以控制是否进行缓存操作。

  • condition:在满足条件时才进行缓存。
  • unless:在满足条件时不进行缓存。

示例

@Cacheable(value = "users", key = "#id", condition = "#id > 10", unless = "#result.username == 'admin'")
public User getUserById(Long id) {
    // 方法实现
}

在上述示例中:

  • 只有当id > 10时,方法执行结果才会被缓存。
  • 即使满足condition条件,如果result.username == 'admin',则不缓存结果。

7.3 缓存同步与异步

在分布式系统中,缓存的一致性和同步性是至关重要的。Spring Cache本身不直接提供同步机制,但可以通过结合其他工具实现。

方案

  • 使用消息队列(如Kafka、RabbitMQ)同步缓存更新。
  • 利用分布式锁(如Redis的RedLock)防止缓存击穿和缓存穿透。
  • 实现基于事件驱动的缓存更新策略。

7.4 缓存与事务的结合

在涉及事务的操作中,缓存的更新需要与事务保持一致性。

方案

  • 缓存更新操作应在事务提交后执行,确保数据的一致性。
  • 使用@CacheEvict的beforeInvocation属性控制缓存清除的时机。

示例

@CacheEvict(value = "users", key = "#id", beforeInvocation = false)
@Transactional
public void deleteUser(Long id) {
    userRepository.deleteById(id);
}

在上述示例中,缓存清除操作将在事务提交后执行,确保数据成功删除后再清除缓存。

8. 总结

本文,我们分析了缓存技术,它在提升应用性能、降低数据库压力、改善用户体验方面发挥着重要作用。

另外,我们重点分析了 Spring中 5个核心的缓存注解以及示例分析,Spring通过提供全面的缓存抽象和简洁的缓存注解,使得开发者能够轻松地集成和管理缓存机制。

相关推荐

音视频命令转换工具 - FFmpeg

随着自媒体兴起,许多人会自拍视频或者找视频素材裁剪,配上背景音乐或解说,加上各种特效边框,处理后再生成新的视频文件,发布到各大平台。生成的原始视频文件都很大,我们需要转换格式或者压缩大小,便于上传或者...

视频剪辑软件,如何批量将h264转换为h265格式的视频

最近有很多朋友在问,因为剪辑、或者上传的原因,需要将视频编码转换成H265格式编码,该怎么操作呢?不知道怎么办的宝贝们,下面请随小编一起来试试吧。需要哪些工具?安装一个媒体梦工厂视频素材若干怎么快速剪...

1080秒变4K,让PotPlayer开启“超分辨率”播放视频文件

大家好,我是大卫呆。1080P的视频能秒变4K视频吗?通过上期节目的实机测试,答案是:...

视频编码H.265与H.264的区别-------深入浅出说监控

我们在购买监控摄像头或者录像机产品的时候,一般情况下,经销商会问你是要H.265编码格式与H.264编码格式的,很多人都会好奇,什么是H.265和H.264?他们的实际效果有什么区别?今天就从定义和作...

H.265已落后!下一代视频技术实现重大突破

来源:快科技下一代视频技术实现重大突破。从阿里云官微获悉,阿里达摩院XG实验室参与制定的新一代国际视频编码标准H.266(VVC)出炉,同等画质下将节省近50%传输流量,清晰度越高,码率节省越多。前不...

VP9 或 H.265 的 6 个比较点

直播很复杂。广播流和通过Internet传输流的整个过程涉及一系列可以采用多种格式的方法。一个重要的组件是用于媒体文件编码和解码的编解码器。编解码器还定义了可用于进行流式传输的工具类型。大大简化流...

融合通信系统播放不了H.265视频怎么解决

在融合通信项目中,视频的融合是很多项目的落地要求,随着技术的进步,需要融合的视频也是多种多样,很多项目中需要接入视频监控,布控球,无人机,视频会议等视频资源。这些视频资源使用不同的技术,不同的传输协议...

别被忽悠了!视频编码H.265与H.264的区别有多大?看完你就懂了

相信大家都听过H.265和H.264这两种编码,也看过专业术语的解释。包括电视机都会标注支持H.265格式4K视频编码,视频监控系统也会标注支持H.265。但是还是有很多人不知道什么是视频编码H.26...

视频行业迎来巨变!H.265将被淘汰,电视及流媒体全受影响

这两天,姐夫自己遇到一个问题,从一些流媒体网站上下载来的视频,比如油管上的视频,居然无法正常在Windows10上播放,必须要重新下载一个视频编码插件才行。甚至于这些视频都无法通过现有的编辑软件去做...

对于Mybaits缓存的理解

...

你居然还去服务器上捞日志,搭个 Graylog 日志收集系统不香么

一、前言...

Java 代理从 0 到彻底搞懂

一、为什么出现代理?咱们先抛开编程,想象一下生活中的场景。假如你是一位大明星,每天都有无数的活动邀约、采访请求,还有各种商务合作的洽谈。要是你亲自去处理这些事情,那你哪还有时间去拍戏、唱歌、提升自己的...

SpringBoot系列——cache缓存

  前言  日常开发中,缓存是解决数据库压力的一种方案,通常用于频繁查询的数据,例如新闻中的热点新闻,本文记录springboot中使用cache缓存。...

Spring的缓存帝国,得益于这 5个注解!

在微服务,分布式的大环境下,缓存绝对是提升系统性能的关键手段,Spring作为Java生态中最流行的企业级应用框架,它是如何实现缓存的呢?这篇文章,我们将深入探讨Spring中5个核心的缓存注解...

JVM缓存EhCache在实际业务系统中的应用及复杂场景探讨

本文将介绍JVM缓存EhCache的基本概念、原理以及在实际业务系统中的使用。文章将重点讨论EhCache在复杂场景下的应用,并提供Java语言实现的示例。1.JVM缓存EhCache简介EhCa...