作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。
多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。
欢迎 点赞✍评论⭐收藏
SpringBoot 领域知识
链接 | 专栏 |
---|---|
SpringBoot 专业知识学习一 | SpringBoot专栏 |
SpringBoot 专业知识学习二 | SpringBoot专栏 |
SpringBoot 专业知识学习三 | SpringBoot专栏 |
SpringBoot 专业知识学习四 | SpringBoot专栏 |
SpringBoot 专业知识学习五 | SpringBoot专栏 |
SpringBoot 专业知识学习六 | SpringBoot专栏 |
SpringBoot 专业知识学习七 | SpringBoot专栏 |
SpringBoot 专业知识学习八 | SpringBoot专栏 |
SpringBoot 专业知识学习九 | SpringBoot专栏 |
SpringBoot 专业知识学习十 | SpringBoot专栏 |
SpringBoot 专业知识学习十一 | SpringBoot专栏 |
SpringBoot 专业知识学习十二 | SpringBoot专栏 |
SpringBoot 专业知识学习十三 | SpringBoot专栏 |
@Cacheable 注解是 Spring 框架提供的一个注解,用于实现方法级别的缓存功能。当方法被调用时,@Cacheable 注解会首先在缓存中查找相应的值,而不是执行方法的逻辑。如果找到了缓存中的值,则直接返回该值给调用方,而不执行方法体;如果缓存中不存在该值,则执行方法的逻辑,并将方法的返回值存储到缓存中,以供下次的调用使用。
@Cacheable 注解可以应用于公共方法,用于缓存方法的返回值,避免重复的计算或查询操作,提高系统的性能和响应速度。它基于键值对的形式存储和获取数据,通过指定缓存的名称、缓存的键以及可选的条件,可以进行有效的缓存管理。
例如,以下是一个使用 @Cacheable 注解的示例:
@Service
public class UserService {
@Cacheable("users")
public User getUserById(long userId) {
// 从数据库查询用户
// ...
return user;
}
}
在上述示例中,方法 getUserById()
使用了 @Cacheable 注解,并指定了缓存的名称为 “users”。当方法被调用时,Spring 会先检查缓存中是否存在键为 userId 的用户对象,如果存在,则直接从缓存中取出并返回;如果不存在,则执行方法的逻辑,从数据库中查询用户,并将查询结果放入缓存中。
通过使用 @Cacheable 注解,可以避免重复查询数据库的开销,提高系统的性能和响应速度。同时,@Cacheable 注解还提供了丰富的缓存管理功能,可以设置缓存的过期时间、条件判断等,以满足不同的业务需求。
@Cacheable 注解的主要作用是实现方法级别的缓存,可以有效地提高系统的性能、降低数据库等资源的压力,并提升响应速度和用户体验。具体来说,它可以帮助我们避免以下问题:
1.减少重复查询的开销:如果多次调用同一个方法,但是方法的参数相同,那么每次调用都会执行查询操作。通过使用缓存,并在第一次调用之后将返回值存储在缓存中,可以避免重复查询的开销,提高系统的性能和响应速度。
2.缓解数据库等资源的压力:当系统的访问量较大时,如果每次访问都直接查询数据库等资源,会对系统的性能和稳定性造成很大的影响,甚至可能导致崩溃。通过使用缓存,并将查询结果存储在缓存中,可以有效地缓解数据库等资源的压力,提高系统的可用性和稳定性。
3.提升用户体验:用户在访问网站或使用应用时,通常希望能够快速地获取数据并获得反馈。通过使用缓存,并在第一次访问之后将数据存储在缓存中,可以提升用户的体验,减少用户等待的时间。
除了上述作用之外,@Cacheable 注解还具有其它的优点,例如**易于实现、易于维护、可扩展性强**等。在实际开发中,我们可以根据具体业务需求,灵活地使用 @Cacheable 注解,来实现缓存功能,提升系统的性能和响应速度。
在 Spring Boot 应用中,使用 @Cacheable 注解实现缓存功能非常简单。以下是基本步骤:
1.添加相关依赖
在 pom.xml 配置文件中添加 Spring Boot Cache、JCache 和 Ehcache 依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>javax.cachegroupId>
<artifactId>cache-apiartifactId>
dependency>
<dependency>
<groupId>org.ehcachegroupId>
<artifactId>ehcacheartifactId>
dependency>
dependencies>
2.配置缓存
在 application.properties 或 application.yml 配置文件中配置缓存相关的属性,例如缓存名称、缓存类型、过期时间等:
# 配置基于 Ehcache 的缓存
spring.cache.type=ehcache
# 配置缓存名称
spring.cache.cache-names=users
# 配置缓存过期时间,单位为秒
spring.cache.ehcache.config=classpath:ehcache.xml
上述配置说明:
spring.cache.type
:指定使用的缓存类型,可以是 simple
或 ehcache
等。这里使用了 ehcache
。spring.cache.cache-names
:指定缓存的名称,可以是一个或多个。spring.cache.ehcache.config
:指定 Ehcache 的配置文件路径。3.标记需要缓存的方法
在需要缓存的方法上添加 @Cacheable 注解,并指定缓存的名称和键:
@Service
public class UserService {
@Cacheable(value = "users", key = "#userId")
public User getUserById(long userId) {
// 从数据库查询用户
// ...
return user;
}
}
上述代码说明:
@Cacheable(value = "users", key = "#userId")
:指定了缓存名称为 users,缓存键为方法的参数 userId。这样,当该方法被调用时,Spring 会首先在缓存中查找键为 userId 的缓存,如果找到了,则直接返回缓存中的值;如果没有找到,则执行方法的逻辑,并将方法的返回值存储在缓存中,以备后续使用。至此,我们就已经完成了在 Spring Boot 应用中使用 @Cacheable 注解实现缓存功能的步骤。使用 @Cacheable 注解可以简单几步实现缓存功能,并提高应用的性能和响应速度,是进行缓存优化的一种常见方式。
@Cacheable 注解的实现原理是基于 AOP(面向切面编程)技术。在 Spring 中,我们可以使用 AOP 对方法进行增强,例如在方法执行前后/异常时,执行额外的逻辑。而使用 @Cacheable 注解,则是在被注解的方法执行前,先检查是否存在缓存。若存在,直接返回缓存中的结果;若不存在,则运行方法,并将结果缓存起来。
具体来说,当使用 @Cacheable 注解标注方法时,Spring AOP 会切入该方法并触发一个切点(Pointcut),进而执行缓存逻辑,这个切面类叫做 CacheInterceptor。 CacheInterceptor 类包含了 CacheManager 和 Cache 对象,它根据给定的缓存名从 CacheManager 中获取缓存。如果缓存名对应的 Cache 对象不存在,则创建新的 Cache 对象,并将其添加到 CacheManager 中。接着,CacheInterceptor 类会检查缓存中是否已经存在给定的键对应的 Entry(实际数据存储在内部的 Entry 中),如果存在,则直接返回缓存中保存的数据;否则,方法会被执行,并且方法的返回值将被添加到缓存中。
总体来说,@Cacheable 注解的实现原理可以概括如下:
1.当方法被调用时,检查 Cache 中是否已经存在指定的缓存键。
2.如果缓存存在,则直接从缓存中获取结果并返回。方法体中的实际逻辑不会执行。
3.如果缓存不存在,则执行方法体中的逻辑,并将结果保存至缓存中。
4.当下一次执行缓存相同的方法时,自动使用缓存中存储的数据,避免了重复执行逻辑,并提高了方法的执行效率。
需要注意的是,在缓存的实现过程中,会涉及到缓存的清除、更新等操作,因此,缓存的实现不是那么简单的。在实际场景中,还需要结合使用 @CachePut 和 @CacheEvict 等注解来实现完整的缓存机制。
**缓存击穿**是指当一个非常热门的缓存键失效时,同时有大量的请求涌入数据库或其他后端系统,导致系统负载过高,甚至引发服务不可用的情况。为了避免缓存击穿,可以考虑以下几种策略:
1.设置适当的过期时间和预加载:为缓存设置适当的过期时间,避免缓存无限期存储,并考虑在缓存过期之前提前预加载数据,以确保缓存数据的实时性。
2.使用互斥锁(Mutex Lock):当缓存键失效时,在查询后端系统之前,使用互斥锁来保证只有一个线程能够查询后端并更新缓存,其他线程等待并共享结果。
3.基于布隆过滤器进行缓存穿透预防:使用布隆过滤器在缓存层面进行简单的校验,过滤掉那些根本不存在于后端系统的请求,减轻数据库负载。
4.异步更新缓存:当缓存键失效时,不阻塞请求并直接返回旧的缓存数据,然后使用后台任务异步更新缓存,提高请求的响应速度。
5.搭建分布式缓存:将缓存分布到多个节点上,避免单一缓存节点失效导致的缓存击穿问题,并且可以通过负载均衡策略来分散请求。
6.合理设置缓存容量:合理估计缓存容量,根据实际需求设置缓存容量大小,避免大规模缓存失效导致的负载问题。
**缓存雪崩**是指当大量缓存数据在同一时间内过期或者失效时,大量的请求涌入数据库或者后端系统,导致系统负载过高,可能会引发服务不可用的情况。为了避免缓存雪崩,可以考虑以下几种策略:
1.设置随机的缓存失效时间:不同的缓存数据设置不同的过期时间,避免所有缓存数据在同一时间内过期导致缓存雪崩。
2.采用热点数据预加载策略:在系统运行期间,统计出热点数据并预先加载到缓存中,确保热点数据在缓存中存在而不是在同一时间内全部失效。
3.采用缓存数据持久化策略:除了缓存,系统还可以在本地或者远端存储备份数据。当缓存失效后,可以从备份数据中加载,避免缓存雪崩的影响。
4.控制缓存数据的并发更新:为缓存数据设置互斥锁,避免多个请求并发更新导致数据库负载过高。
5.使用多级缓存:在系统中,使用 多级缓存(例如:一级缓存、二级缓存、三级缓存等)的方式来降低热点数据单一缓存失效对整个系统造成的冲击。
@Cacheable 注解是 Spring 框架提供的缓存注解之一,用于将方法的返回结果缓存在缓存中,下次相同的方法调用时可以直接从缓存中获取结果,而不需要再执行方法体。
@Cacheable 注解的 key 属性用于指定缓存的键(key),该键用于唯一标识缓存中存储的数据。在使用 @Cacheable 注解时,Spring 会根据指定的键来查找缓存中是否存在对应的数据,如果存在则直接返回缓存中的数据,而不会执行被注解的方法。
key 属性支持 SpEL(Spring Expression Language)表达式,这样可以根据方法的参数、返回值等信息动态地生成缓存键。通过 SpEL 表达式,可以根据具体的业务需求来构建唯一的键,以确保不同的方法调用可以在缓存中正确地区分开来。
例如,假设有一个通过用户 ID 查询用户信息的方法,可以使用 @Cacheable 注解,并将用户 ID 作为 key 属性的一部分,以便将用户信息缓存在不同的缓存键中,如:
@Cacheable(value = "userCache", key = "'user:' + #userId")
public User getUserById(String userId) {
// ...
// 查询用户信息的具体逻辑
// ...
}
在上述示例中,key = "'user:' + #userId"
指定了缓存的键为以字符串 'user:'
为前缀,后面拼接上参数 #userId
的值。这样不同的用户 ID 将会生成不同的缓存键,确保查询结果被正确地缓存和获取。
请注意,SpEL 表达式中的 #userId
表示方法参数 userId 的值。您可以根据实际情况来构建适合的缓存键,以满足具体的业务需求。
@Cacheable 注解是 Spring 框架提供的缓存注解之一,用于将方法的返回结果缓存在缓存中。其中,value 属性用于指定缓存的名称。可以通过 value 属性设置成一个字符串类型的值,并在 @Cacheable
中用作缓存的名称。
在 Spring Boot 应用中,可以使用 @CacheConfig
注解或在配置文件中配置 spring.cache.cache-names
属性来设置多个缓存。当使用多个缓存时,可以通过 @Cacheable
注解的 value 属性来指定相应的缓存名称。例如:
@Cacheable(value = "userCache")
public User getUserById(String userId) {
// ...
// 查询用户信息的具体逻辑
// ...
}
在上述示例中,@Cacheable(value = "userCache")
指定了缓存的名称为 userCache
。这个名称在应用中必须是唯一的,用于标识不同的缓存实例。
请注意,value 属性接受一个字符串类型的值,可自定义。它不是指缓存的实际数据,而是表示缓存实例的名称。因此,应该为每个缓存实例选择一个明确的名称便于维护和管理。
如果没有指定 value 属性,则会使用默认的缓存名称。在 Spring Boot 中,默认的缓存名称为 simple
,如果 @Cacheable
注解中没有指定其他名称,则数据会被存储在名为 simple
的缓存中。
@Cacheable 注解支持使用 SpEL(Spring Expression Language)表达式来配置缓存。
在 @Cacheable 注解中使用 SpEL 表达式的方式是在 key 属性或 condition 属性中使用。通过 SpEL 表达式,可以根据方法的参数、返回值等信息动态地生成缓存键(key)或者控制缓存的条件。
1.使用 SpEL 表达式生成缓存键:
@Cacheable(value = "userCache", key = "'user:' + #userId")
public User getUserById(String userId) {
// ...
// 查询用户信息的具体逻辑
// ...
}
在上述示例中,key = "'user:' + #userId"
表达式通过拼接字符串 'user:'
和方法参数 #userId
的值来生成缓存键。
2.使用 SpEL 表达式控制缓存条件:
@Cacheable(value = "userCache", condition = "#result != null")
public User getUserById(String userId) {
// ...
// 查询用户信息的具体逻辑
// ...
}
在上述示例中,condition = "#result != null"
表达式表示只有方法的返回值不为 null 时才会进行缓存。如果结果为 null,那么不会将结果缓存起来。
使用 SpEL 表达式可以根据具体的业务需求来动态地配置缓存。可以根据方法的参数、返回值、调用对象等信息来生成或者控制缓存的键和条件。
需要注意的是,SpEL 表达式必须放在单引号(')或者双引号(")中,并且要以 # 开头,以表示占位符。在表达式中,可以使用方法的参数名、返回值名、调用对象名等表示所需的信息。
当在 @Cacheable 注解中使用 SpEL 表达式时,可以使用如下常用的变量和方法:
方法参数:使用 #参数名
的形式表示方法的参数值。例如,#userId
表示方法的参数 userId 的值。
返回值:使用 #result
表示方法的返回值。可以在 condition 属性中使用该变量来控制缓存的条件。
注解属性:可以使用 @注解名.属性名
的形式获取注解的属性值。
类名和方法名:可以使用 #root.targetClass
获取目标类的类对象,使用 #root.methodName
获取方法名。
执行上下文:可以使用 #root.args
获取方法的所有参数数组,使用 #root.caches
获取所有相匹配的缓存实例。
下面是一个示例,展示如何在 @Cacheable 注解中使用 SpEL 表达式:
@Cacheable(value = "userCache", key = "'user:' + #userId", condition = "#result != null")
public User getUserById(String userId) {
// ...
// 查询用户信息的具体逻辑
// ...
}
在上述示例中,使用了三个 SpEL 表达式:
key = "'user:' + #userId"
:通过拼接字符串 'user:'
和方法参数 #userId
的值来生成缓存键。condition = "#result != null"
:只有方法的返回值不为 null 时才会进行缓存。除了上述示例中的用法,还可以根据具体的业务需求来使用其他的 SpEL 表达式来配置缓存。可以参考 SpEL 表达式语法和 Spring 框架文档来了解更多用法和限制。
@Cacheable
注解的 condition
属性用于控制是否执行方法的缓存逻辑,即是否将方法的结果放入缓存中。
通过在 condition
属性中设置一个 SpEL(Spring Expression Language)表达式,可以根据方法的返回值、参数等条件来决定是否进行缓存。只有在满足条件的情况下,才会将方法的返回值缓存起来。
条件表达式的计算结果应该是一个布尔值(true 或 false)。如果计算结果为 true,则将方法的返回值进行缓存;如果计算结果为 false,则不会进行缓存。
以下是一个使用 condition
属性的示例:
@Cacheable(value = "userCache", condition = "#result != null")
public User getUserById(String userId) {
// ...
// 查询用户信息的具体逻辑
// ...
}
在上述示例中,condition = "#result != null"
表达式表示只有方法的返回值不为 null 时才会进行缓存。如果结果为 null,那么不会将结果缓存起来。
condition
属性可以根据具体的业务需求来进行灵活配置。可以使用 SpEL 表达式根据方法的返回值、参数、调用对象等信息来定义缓存的条件。
使用 condition
属性可以帮助在缓存中避免存储无效或无意义的数据。通过根据条件动态地控制缓存的逻辑,可以确保缓存中只包含有效的数据。这可以提升缓存的效率和可靠性,以及减少对缓存的占用和维护成本。
@Cacheable
注解的 sync
属性用于控制是否启用同步模式来保证并发安全。
当 sync
属性设置为 true
时,表示启用同步模式。在同一时间内,只允许一个线程执行带有 @Cacheable
注解的方法,其他线程将会被阻塞,直到缓存操作完成并返回结果。
默认情况下,sync
属性的值为 false
,即不启用同步模式。在多线程并发访问的情况下,如果有多个线程同时触发了带有 @Cacheable
注解的方法,那么这些线程会并发地执行方法,并且会将结果缓存起来。这可能会导致并发安全问题,例如重复缓存相同的数据,浪费资源等。
使用同步模式可以确保在进行缓存操作时只有一个线程执行,从而避免并发安全问题。但同时也会带来一定的性能开销和响应时间延迟,因为其他线程需要等待同步锁的释放才能继续执行。
下面是一个示例,展示如何在 @Cacheable
注解中使用 sync
属性:
@Cacheable(value = "userCache", sync = true)
public User getUserById(String userId) {
// ...
// 查询用户信息的具体逻辑
// ...
}
在上述示例中,通过设置 sync = true
启用同步模式。当多个线程同时触发 getUserById
方法时,只会允许一个线程执行方法并进行缓存操作,其他线程将被阻塞。
需要注意的是,在并发访问下启用同步模式可能会降低性能。因此,需要根据具体的业务需求和场景来综合考虑是否需要启用同步模式。
当使用 @Cacheable
注解的 sync
属性时,需要注意以下几点:
并发安全性:启用同步模式可以确保在并发访问时只有一个线程执行方法并进行缓存操作,从而避免并发安全问题。这对于一些读写操作都需要加锁的场景可能是必要的。
性能影响:启用同步模式会导致其他线程在缓存操作期间被阻塞,直到方法返回结果。这可能会增加响应时间延迟,并降低系统的吞吐量。因此,需要根据具体情况评估并发性能与应用的要求。
缓存一致性:启用同步模式可以确保缓存的一致性,因为只有一个线程执行缓存操作。但在某些情况下,例如对于读多写少的场景,可以使用异步缓存来提高并发性能,而牺牲一些缓存的一致性。
其他缓存注解:@CachePut
和 @CacheEvict
注解不支持 sync
属性。@CachePut
注解用于更新缓存中的数据,而 @CacheEvict
注解用于移除缓存中的数据。在这两种情况下,是否启用同步模式取决于使用场景和并发要求。
需要根据具体的业务需求和并发场景来决定是否使用 @Cacheable
注解的 sync
属性。如果需要保证并发安全性,并且可以接受一些性能开销,那么启用同步模式可能是一个不错的选择。否则,可以根据应用的实际情况来选择是否启用同步模式。
@Cacheable
注解中的 key
属性用于定义缓存的键,它通常是由一个或多个属性值组合而成的字符串。这个字符串在执行缓存操作时起到了唯一标识一个缓存实体的作用。
通常情况下,@Cacheable
注解中的 key
属性会在编写代码时预先定义好,难以在运行时对其进行修改。但是,Spring 提供了一些动态缓存的解决方案,可以在运行时动态生成或修改缓存的键,以满足特定的业务需求。
下面介绍几种常见的动态缓存方案:
1.SpEL 表达式:在 @Cacheable
注解中使用 SpEL 表达式,可以根据方法的参数、返回值、执行对象等信息动态生成缓存的键。例如:
@Cacheable(value = "userCache", key = "#userId")
public User getUserById(String userId) {
// ...
// 查询用户信息的具体逻辑
// ...
}
在上述示例中,key = "#userId"
表达式表示使用方法的 userId
参数作为缓存的键。这种方式可以根据参数的不同来动态生成不同的缓存键。
2.缓存前缀:可以在缓存的键前面添加一个固定的前缀,以便动态修改缓存的键。可以使用 Spring 提供的 CacheManager
接口中的 getCache()
方法来获取要操作的缓存对象,并使用 put()
和 get()
方法来操作缓存数据。
@Autowired
private CacheManager cacheManager;
public void someMethod(String userId) {
Cache userCache = cacheManager.getCache("userCache");
String cacheKey = "user:" + userId; // 动态生成缓存键
User user = userCache.get(cacheKey, User.class);
if (user == null) {
// 查询数据库,并将结果放入缓存中
user = userService.getUserById(userId);
userCache.put(cacheKey, user);
}
}
在上述示例中,cacheKey
变量是根据 userId
参数动态生成的键值,可以在需要的时候灵活地进行修改。
无论使用哪种动态缓存方案,都需要考虑缓存的一致性和有效性。需要确保缓存的键在各种情况下都是唯一的,并且不会出现重复或冲突的情况。动态缓存要求在实际应用中谨慎使用,在需要动态修改缓存的键时才进行使用。