@Cacheable
是Spring框架中用于缓存方法返回结果的注解,它可以显著提高应用程序的性能,特别是对于一些计算密集型或频繁调用且结果不经常变化的方法。以下是关于@Cacheable
的详细介绍:
基本使用
- 添加依赖:使用
@Cacheable
注解前,需要在项目的pom.xml
中添加Spring Cache依赖,例如使用Spring Boot时,可以添加以下依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 启用缓存:在Spring Boot的主应用程序类上添加
@EnableCaching
注解来启用缓存功能,如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
- 使用
@Cacheable
注解方法:在需要缓存结果的方法上添加@Cacheable
注解,如下:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class MyService {@Cacheable("myCache")public String getCachedData(String key) {// 模拟耗时操作try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}return "Data for key: " + key;}
}
在上述示例中,@Cacheable("myCache")
注解告诉Spring将该方法的结果存储在名为myCache
的缓存中。当这个方法被调用时,Spring首先检查myCache
中是否已经有了对应key
的缓存结果。如果有,直接从缓存中返回结果,而不执行方法体;如果没有,则执行方法并将结果存储在缓存中。
注解属性
以下是对 @Cacheable
注解的主要参数的详细介绍:
1. value
- 作用:
- 指定缓存的名称。它是
@Cacheable
注解最常用的参数之一,用来标识一个或多个缓存存储区域。当方法的返回结果需要存储时,会被存储在这些指定的缓存名称下。cacheNames
参数与之相同。
- 指定缓存的名称。它是
- 使用示例:
在上述示例中,方法@Cacheable(value = "userCache") public User getUserById(Long id) {// 业务逻辑 }
getUserById
的结果将被存储在名为userCache
的缓存区域中。 - 多值使用:
- 可以使用一个数组来指定多个缓存名称,此时结果会被存储在多个缓存区域中。
当调用@Cacheable(value = {"cache1", "cache2"}) public String getSomeData(String key) {// 业务逻辑 }
getSomeData
方法时,其结果会同时存储在cache1
和cache2
缓存区域中。
2. key
- 作用:
- 用于指定缓存存储和检索的键。默认情况下,会使用方法的参数作为键,但可以使用 SpEL(Spring Expression Language)自定义键的生成表达式。
- 使用示例(使用参数作为键):
这里使用@Cacheable(value = "userCache", key = "#id") public User getUserById(Long id) {// 业务逻辑 }
#id
作为键,即调用getUserById
方法时传入的id
参数将作为缓存的键。 - 使用示例(使用多个参数作为键):
此示例中,将使用@Cacheable(value = "userCache", key = "#firstName + '-' + #lastName") public User getUserByName(String firstName, String lastName) {// 业务逻辑 }
firstName
和lastName
参数组合(使用-
分隔)作为缓存的键。 - 使用示例(使用方法信息作为键):
这里使用方法名称和@Cacheable(value = "userCache", key = "#root.methodName + '_' + #id") public User getUserById(Long id) {// 业务逻辑 }
id
参数组合作为键,例如对于getUserById(1L)
的调用,键可能是getUserById_1
。 - 特殊键生成器使用:
- 可以使用自定义的键生成器,需要实现
org.springframework.cache.interceptor.KeyGenerator
接口,并在@Cacheable
注解中引用。
@Cacheable(value = "userCache", keyGenerator = "myCustomKeyGenerator") public User getUserById(Long id) {// 业务逻辑 }
- 可以使用自定义的键生成器,需要实现
3. condition
- 作用:
- 用于指定一个 SpEL 表达式,当表达式的结果为
true
时,才会进行缓存操作。可以根据方法的参数、返回值或其他信息来决定是否缓存结果。
- 用于指定一个 SpEL 表达式,当表达式的结果为
- 使用示例:
仅当@Cacheable(value = "userCache", condition = "#id > 0") public User getUserById(Long id) {// 业务逻辑 }
id
参数大于 0 时,才会将getUserById
方法的结果存储到userCache
中。 - 更复杂的条件示例:
仅当@Cacheable(value = "userCache", condition = "#user.age > 18 && #user.active") public User getUser(User user) {// 业务逻辑 }
user
的年龄大于 18 岁且active
属性为true
时,结果才会被缓存。
4. unless
- 作用:
- 也是使用 SpEL 表达式,当表达式的结果为
true
时,不会将结果存储在缓存中。与condition
相反,condition
决定是否缓存,unless
决定不缓存的情况。
- 也是使用 SpEL 表达式,当表达式的结果为
- 使用示例:
如果@Cacheable(value = "userCache", unless = "#result == null") public User getUserById(Long id) {// 业务逻辑 }
getUserById
方法的返回结果为null
,则不会将结果存储在userCache
中。 - 使用示例(根据结果属性判断):
当@Cacheable(value = "userCache", unless = "#result.name == null") public User getUserById(Long id) {// 业务逻辑 }
getUserById
的返回结果的name
属性为null
时,不会存储该结果。
5. cacheManager
- 作用:
- 可以指定一个特定的缓存管理器来管理缓存操作,用于在多个缓存管理器存在的情况下,选择使用哪个缓存管理器。
- 使用示例:
上述代码中,使用名为@Cacheable(value = "userCache", cacheManager = "customCacheManager") public User getUserById(Long id) {// 业务逻辑 }
customCacheManager
的缓存管理器来管理userCache
中的缓存操作。
6. cacheResolver
- 作用:
- 可以指定一个特定的缓存解析器,用于更灵活地解析缓存操作的存储位置,实现
org.springframework.cache.interceptor.CacheResolver
接口的自定义缓存解析器。
- 可以指定一个特定的缓存解析器,用于更灵活地解析缓存操作的存储位置,实现
- 使用示例:
@Cacheable(value = "userCache", cacheResolver = "myCacheResolver") public User getUserById(Long id) {// 业务逻辑 }
7. sync
- 作用:
- 是一个布尔值参数,用于指示是否以同步方式执行缓存操作。默认为
false
,表示使用异步方式。 - 当设置为
true
时,同一时间内只有一个线程可以执行该方法,其他线程会阻塞等待缓存结果,避免多个线程同时执行该方法。
- 是一个布尔值参数,用于指示是否以同步方式执行缓存操作。默认为
- 使用示例:
@Cacheable(value = "userCache", sync = true) public User getUserById(Long id) {// 业务逻辑 }
这些参数可以灵活组合使用,以满足不同的缓存需求,通过使用 @Cacheable
及其参数,可以在 Spring 应用程序中轻松实现缓存机制,提高系统性能和响应速度,同时也需要根据具体的业务场景和性能需求,合理调整这些参数的使用。
缓存工作原理
- 当一个被
@Cacheable
注解的方法被调用时,Spring的AOP机制会拦截该方法的调用。 - 首先,根据
value
和key
属性查找缓存中是否已经存储了相应的结果。 - 如果缓存中存在结果,则直接返回缓存中的结果,不执行方法体。
- 如果缓存中不存在结果,则执行方法体,将结果存储在缓存中,并返回结果。
适用场景
- 数据访问层:在数据访问层中,对于经常查询但很少修改的数据,如配置信息、字典数据等,可以使用
@Cacheable
进行缓存,减少对数据库的访问。 - 业务逻辑层:对于一些复杂的计算逻辑,如计算用户积分、统计报表等,当输入参数相同时,可以使用
@Cacheable
避免重复计算。
注意事项
- 缓存更新:当被缓存的数据发生变化时,需要使用
@CacheEvict
或@CachePut
等注解来更新或清除缓存,以保证数据的一致性。 - 缓存失效:缓存的存储是有限的,需要考虑缓存失效的策略,如LRU(Least Recently Used)等,避免内存溢出。
- 序列化:存储在缓存中的对象需要是可序列化的,因为缓存存储可能会将对象存储在分布式环境中,如Redis等,确保对象实现了
Serializable
接口或使用其他序列化机制。
与其他缓存注解的配合使用
- @CacheEvict:用于清除缓存,例如:
@CacheEvict(value = "myCache", key = "#key") public void clearCache(String key) {// 清除myCache中指定key的缓存 }
- @CachePut:用于更新缓存,无论缓存中是否存在结果,都会执行方法体,并将结果存储在缓存中,例如:
@CachePut(value = "myCache", key = "#key") public String updateCachedData(String key) {return "Updated data for key: " + key; }
缓存存储的选择
- 可以使用Spring默认的缓存存储,也可以集成外部缓存,如Redis、Ehcache等。集成外部缓存时,需要在配置文件中添加相应的配置,例如使用Redis时:
spring:cache:type: redisredis:cache-null-values: false
通过合理使用@Cacheable
和其他缓存注解,可以有效提高应用程序的性能和响应速度,同时需要根据具体的应用场景和需求,灵活运用这些注解,确保缓存的一致性和有效性。
实际使用案例
以下是一个更完整的 @Cacheable
注解的实际使用案例,结合了一个简单的 Spring Boot 应用程序,包括服务层、控制器和数据访问层,展示如何在不同场景下使用 @Cacheable
注解来提高性能:
1. 添加依赖
首先,在 pom.xml
中添加 Spring Boot 的缓存依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2. 启用缓存
在 Spring Boot 的主应用程序类上添加 @EnableCaching
注解,以启用缓存功能:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching
public class CacheableApplication {public static void main(String[] args) {SpringApplication.run(CacheableApplication.class, args);}
}
3. 数据访问层 (Repository)
假设我们有一个简单的数据访问接口和实现类,模拟从数据库中获取用户信息:
import org.springframework.stereotype.Repository;@Repository
public interface UserRepository {User findById(Long id);
}import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.Map;@Repository
public class UserRepositoryImpl implements UserRepository {private final Map<Long, User> userMap = new HashMap<>();public UserRepositoryImpl() {// 初始化一些用户数据userMap.put(1L, new User(1L, "Alice"));userMap.put(2L, new User(2L, "Bob"));userMap.put(3L, new User(3L, "Charlie"));}@Overridepublic User findById(Long id) {// 模拟从数据库获取数据的延迟try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}return userMap.get(id);}
}
4. 服务层 (Service)
在服务层使用 @Cacheable
注解来缓存 findById
方法的结果:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Cacheable(value = "userCache", key = "#id")public User findUserById(Long id) {return userRepository.findById(id);}
}
5. 控制器 (Controller)
在控制器中调用服务层的方法,观察缓存的效果:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/users/{id}")public User getUserById(@PathVariable Long id) {return userService.findUserById(id);}
}
6. 实体类 (Entity)
定义一个简单的 User
实体类:
import java.io.Serializable;public class User implements Serializable {private Long id;private String name;public User() {}public User(Long id, String name) {this.id = id;this.name = name;}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;}
}
7. 测试缓存效果
- 当你第一次访问
http://localhost:8080/users/1
时,会看到一个大约 2 秒的延迟,因为它会调用UserRepositoryImpl
的findById
方法从模拟的数据库中获取数据。 - 再次访问
http://localhost:8080/users/1
时,会立即返回结果,因为结果已经被缓存,不会再调用findById
方法。
8. 高级使用
- 使用条件缓存:
上述代码中,仅当@Cacheable(value = "userCache", key = "#id", condition = "#id > 0") public User findUserById(Long id) {return userRepository.findById(id); }
id
大于 0 时才会进行缓存。 - 使用
unless
排除缓存结果:
当结果为@Cacheable(value = "userCache", key = "#id", unless = "#result == null") public User findUserById(Long id) {return userRepository.findById(id); }
null
时,不缓存结果。
9. 结合其他缓存注解
- 可以使用
@CachePut
来更新缓存:
当调用import org.springframework.cache.annotation.CachePut; import org.springframework.stereotype.Service;@Service public class UserService {@Autowiredprivate UserRepository userRepository;@Cacheable(value = "userCache", key = "#id")public User findUserById(Long id) {return userRepository.findById(id);}@CachePut(value = "userCache", key = "#user.id")public User updateUser(User user) {// 模拟更新用户信息return userRepository.update(user);} }
updateUser
方法时,会更新缓存中的用户信息。 - 使用
@CacheEvict
来清除缓存:
当调用import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service;@Service public class UserService {@Autowiredprivate UserRepository userRepository;@Cacheable(value = "userCache", key = "#id")public User findUserById(Long id) {return userRepository.findById(id);}@CacheEvict(value = "userCache", key = "#id")public void deleteUser(Long id) {userRepository.delete(id);} }
deleteUser
方法时,会清除userCache
中对应id
的缓存。
10. 缓存管理
- 可以使用不同的缓存管理器或缓存解析器,例如使用 Redis 作为缓存存储:
并在spring:cache:type: redisredis:cache-null-values: false
@Cacheable
中指定:@Cacheable(value = "userCache", cacheManager = "redisCacheManager") public User findUserById(Long id) {return userRepository.findById(id); }
通过以上示例,你可以看到如何在一个实际的 Spring Boot 应用程序中使用 @Cacheable
注解来实现缓存功能,提高应用程序的性能和响应速度,同时根据不同的业务场景和需求,灵活运用 @Cacheable
及其相关注解。你可以通过测试不同的请求和观察日志,来更好地理解缓存的存储、更新和清除操作。