大家好,我是猿java 。
在微服务,分布式的大环境下,缓存绝对是提升系统性能的关键手段之一。Spring作为 Java生态中最流行的企业级应用框架,它是如何实现缓存的呢?这篇文章,我们将深入探讨 Spring中 5个核心的缓存注解。
1. 什么是缓存? 缓存(Cache)是一种存储机制,旨在临时存储数据副本,以便快速访问。缓存一般位于应用程序与数据源(如数据库)之间,能够显著降低数据访问延迟和减轻数据源的压力。
2. 缓存的类型
本地缓存 :存在于应用程序本地内存中,例如使用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会先检查缓存中是否存在对应的键值对,如果存在,则直接返回缓存中的结果;如果不存在,则执行方法,并将结果存入缓存。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Service public class UserService { @Cacheable(value = "users", key = "#id") public User getUserById (Long id) { simulateSlowService(); return new User (id, "John Doe" ); } private void simulateSlowService () { try { Thread.sleep(3000L ); } catch (InterruptedException e) { throw new IllegalStateException (e); } } }
在上述示例中,getUserById
方法被@Cacheable
注解修饰,指定使用users
缓存,并以方法参数id
作为缓存键。首次调用该方法时,缓存中不存在对应的用户信息,方法会被执行并将结果存入缓存。后续相同的调用将直接从缓存中获取结果,避免了重复的业务逻辑执行。
关键属性:
value
/ cacheNames
:指定缓存的名称,可以有多个,表示多个缓存同时生效。
key
:指定缓存的键,支持SpEL表达式,默认基于方法参数生成。
condition
:缓存条件,符合条件的情况下才进行缓存。
unless
:否决缓存条件,符合条件的情况下不缓存。
keyGenerator
:自定义键生成策略。
cacheManager
:指定使用的缓存管理器。
cacheResolver
:指定缓存解析器。
4.2 @CachePut @CachePut
注解同样用于方法级别,但与@Cacheable
不同,它总是执行方法,并将结果存入缓存。@CachePut
适用于需要更新缓存但不影响方法执行结果的场景。
使用示例:
1 2 3 4 5 6 7 8 9 @Service public class UserService { @CachePut(value = "users", key = "#user.id") public User updateUser (User user) { return user; } }
在上述示例中,updateUser
方法被@CachePut
注解修饰,每次调用该方法时,都会执行方法逻辑(更新操作),并将返回的User
对象更新到users
缓存中。这样可以确保缓存中的数据与数据库中的数据保持一致。
关键属性:
与@Cacheable
相同,@CachePut
也支持value
、cacheNames
、key
等属性,用于指定缓存名称、键及其他配置。
4.3 @CacheEvict @CacheEvict
注解用于方法级别,表示在方法执行后,清除指定缓存中的一个或多个条目。它常用于删除操作,以确保缓存中的数据与数据源保持一致。
使用示例:
1 2 3 4 5 6 7 8 @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
注解用于组合多个缓存注解,使得在一个方法上可以执行多个缓存操作。它适用于需要同时执行多个缓存行为的复杂场景。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 @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
注解用于类级别,为该类中的所有缓存注解提供公共配置。例如,可以指定统一的缓存名称、缓存管理器等,减少重复配置的工作量。
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Service @CacheConfig(cacheNames = "users", cacheManager = "cacheManager") public class UserService { @Cacheable(key = "#id") public User getUserById (Long id) { return new 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) :
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-cache</artifactId > </dependency > <dependency > <groupId > net.sf.ehcache</groupId > <artifactId > ehcache</artifactId > <version > 2.10.6</version > </dependency >
配置示例 :
创建一个EhCache配置文件ehcache.xml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <ehcache xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation ="http://ehcache.org/ehcache.xsd" > <diskStore path ="java.io.tmpdir" /> <defaultCache maxEntriesLocalHeap ="1000" eternal ="false" timeToIdleSeconds ="300" timeToLiveSeconds ="600" overflowToDisk ="false" /> <cache name ="users" maxEntriesLocalHeap ="500" timeToLiveSeconds ="3600" eternal ="false" overflowToDisk ="false" /> </ehcache >
Spring配置 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration @EnableCaching public class 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) { return new EhCacheCacheManager (factoryBean.getObject()); } }
5.2 Redis Redis是一种高性能的NoSQL缓存数据库,支持分布式部署,适用于大规模应用场景。
依赖配置(Maven) :
1 2 3 4 5 6 7 8 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-cache</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
配置示例(application.properties) :
1 2 3 4 spring.cache.type =redis spring.redis.host =localhost spring.redis.port =6379 spring.redis.password =yourpassword
Spring配置 :
Spring Boot会自动配置RedisCacheManager
,无需额外配置。如果需要自定义配置,可以如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Configuration @EnableCaching public class 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) :
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-cache</artifactId > </dependency > <dependency > <groupId > com.github.ben-manes.caffeine</groupId > <artifactId > caffeine</artifactId > <version > 3.1.6</version > </dependency >
Spring配置 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration @EnableCaching public class CaffeineCacheConfig { @Bean public Caffeine<Object, Object> caffeineConfig () { return Caffeine.newBuilder() .expireAfterWrite(60 , TimeUnit.MINUTES) .maximumSize(1000 ); } @Bean public CacheManager cacheManager (Caffeine<Object, Object> 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) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <dependency > <groupId > com.h2database</groupId > <artifactId > h2</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-cache</artifactId > </dependency > <dependency > <groupId > net.sf.ehcache</groupId > <artifactId > ehcache</artifactId > <version > 2.10.6</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <scope > provided</scope > </dependency > </dependencies >
6.3 缓存配置 创建ehcache.xml
文件 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <ehcache xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation ="http://ehcache.org/ehcache.xsd" > <diskStore path ="java.io.tmpdir" /> <defaultCache maxEntriesLocalHeap ="1000" eternal ="false" timeToIdleSeconds ="300" timeToLiveSeconds ="600" overflowToDisk ="false" /> <cache name ="users" maxEntriesLocalHeap ="500" timeToLiveSeconds ="3600" eternal ="false" overflowToDisk ="false" /> </ehcache >
配置类 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration @EnableCaching public class 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) { return new EhCacheCacheManager (cm); } }
6.4 实体和仓库 用户实体类 :
1 2 3 4 5 6 7 8 9 @Entity @Data @NoArgsConstructor @AllArgsConstructor public class User { @Id private Long id; private String username; }
用户仓库接口 :
1 2 3 public interface UserRepository extends JpaRepository <User, Long> { User findByUsername (String username) ; }
6.5 服务层与缓存注解应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Service @CacheConfig(cacheNames = "users") public class 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) { throw new IllegalStateException (e); } } }
在上述示例中:
getUserById
和getUserByUsername
方法被@Cacheable
注解修饰,表示查询用户时会先从缓存中查找,若缓存不存在则执行数据库查询并将结果缓存在users
缓存中。
updateUser
方法被@CachePut
注解修饰,表示更新用户信息时,会将更新后的用户对象写入缓存。
deleteUser
方法被@CacheEvict
注解修饰,表示删除用户时,会从缓存中移除对应的用户信息。
6.6 控制层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public ResponseEntity<User> getUserById (@PathVariable Long id) { User user = userService.getUserById(id); return ResponseEntity.ok(user); } @GetMapping("/username/{username}") public ResponseEntity<User> getUserByUsername (@PathVariable String username) { User user = userService.getUserByUsername(username); return ResponseEntity.ok(user); } @PostMapping public ResponseEntity<User> addUser (@RequestBody User user) { User savedUser = userService.updateUser(user); return ResponseEntity.status(HttpStatus.CREATED).body(savedUser); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser (@PathVariable Long id) { userService.deleteUser(id); return ResponseEntity.noContent().build(); } }
6.7 测试缓存效果
启动应用程序 。
调用GET /api/users/{id}
接口 :
首次调用会触发数据库查询并缓存结果。
第二次调用相同的接口,将直接从缓存中获取用户信息,响应速度更快。
调用POST /api/users
接口更新用户 :
更新操作会通过@CachePut
注解将新的用户信息更新到缓存中。
调用DELETE /api/users/{id}
接口删除用户 :
删除操作会通过@CacheEvict
注解从缓存中移除用户信息。
通过上述步骤,可以验证缓存的实际效果,发现读取操作的响应时间明显降低。
7. 增强功能 7.1 自定义缓存键生成策略 默认情况下,Spring根据方法的参数生成缓存键。对于复杂的业务场景,可能需要自定义缓存键生成策略。
自定义KeyGenerator :
1 2 3 4 5 6 7 8 9 @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 :
1 2 3 4 @Cacheable(cacheNames = "users", keyGenerator = "customKeyGenerator") public User getUser (Long id, String type) { }
7.2 缓存条件与排除 通过condition
和unless
属性,可以控制是否进行缓存操作。
condition
:在满足条件时才进行缓存。
unless
:在满足条件时不进行缓存。
示例 :
1 2 3 4 @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
属性控制缓存清除的时机。
示例 :
1 2 3 4 5 @CacheEvict(value = "users", key = "#id", beforeInvocation = false) @Transactional public void deleteUser (Long id) { userRepository.deleteById(id); }
在上述示例中,缓存清除操作将在事务提交后执行,确保数据成功删除后再清除缓存。
8. 总结 本文,我们分析了缓存技术,它在提升应用性能、降低数据库压力、改善用户体验方面发挥着重要作用。
另外,我们重点分析了 Spring中 5个核心的缓存注解以及示例分析,Spring通过提供全面的缓存抽象和简洁的缓存注解,使得开发者能够轻松地集成和管理缓存机制。
9. 学习交流 如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。