导读:本期聚焦于松松建站创作的《Spring Boot整合缓存时,@EnableCaching与@Cacheable如何正确配合?EnableCacheable又是怎么回事?》,敬请观看详情。把开启缓存的注解写成@EnableCacheable,是Spring Boot缓存整合中最常见的启动错误之一。Spring Cache抽象中真正负责开启缓存管理的注解是@EnableCaching,它会在容器中注册缓存拦截器,而@Cacheable、@CachePut、@CacheEvict分别对应缓存的读取、更新和删除。正确做法是先引入spring-boot-starter-cache依赖,再在启动类或配置类标注@EnableCaching,最后在需要缓存的方法上使用@Cacheable并设置合理的cacheNames和key。本文通过完整代码示例演示Spring Boot整合缓存的流程,包括默认缓存管理器、SpEL表达式生成key、条件缓存、缓存更新失效,以及类内部调用导致注解不生效等常见问题的排查。

在Spring Boot项目中实现方法级缓存,通常依赖Spring Cache抽象。它并不是某个具体缓存产品的实现,而是一套统一的注解和接口规范。要启用这套规范,必须在配置类上标注@EnableCaching,而不是@EnableCacheable。不少开发者在刚开始接触时容易把两个名字记混,导致缓存配置不生效。@EnableCacheable并不是Spring官方提供的注解,如果在代码中误用,编译阶段可能报找不到符号,或者即便通过某些自定义注解绕过,运行期也不会触发缓存逻辑。下面从概念、依赖、基础用法和进阶配置几个层面展开说明。

Spring Boot整合缓存时,@EnableCaching与@Cacheable如何正确配合?EnableCacheable又是怎么回事?

一、先厘清注解:@EnableCaching才是开启缓存的正确姿势

Spring Cache抽象的核心入口是@EnableCaching。它通过@Import引入CachingConfigurationSelector,在Spring容器中注册缓存相关的BeanPostProcessor和拦截器。当被标注@Cacheable的方法被调用时,代理类会先检查缓存中是否已有数据,有则直接返回,没有则执行原方法并将结果写入缓存。

@EnableCacheable这个名字并不存在。如果你看某些旧教程或内部封装代码里出现类似写法,要么是拼写错误,要么是自定义了同名注解但未接入Spring Cache的拦截机制。要判断注解是否真正生效,可以观察日志中是否出现CacheAspectSupport相关代理,或者调试时方法是否被代理。

启动类上标注@EnableCaching的示例代码如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class CacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheApplication.class, args);
    }
}

二、引入依赖并配置缓存管理器

Spring Boot默认提供自动配置,只要引入spring-boot-starter-cache,就能在无任何额外配置的情况下使用基于ConcurrentHashMap的本地缓存。这种缓存适合单体应用和小数据量场景,重启即失效。对于分布式或需要持久化的场景,可以切换为Redis、Caffeine、Ehcache等。

pom.xml中添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

如果使用Redis,再添加spring-boot-starter-data-redis,并在配置文件中指定缓存类型:

spring:
  cache:
    type: redis
  redis:
    host: 127.0.0.1
    port: 6379

Spring Boot会根据classpath自动创建CacheManager。默认的ConcurrentMapCacheManager不提供过期时间,数据会一直存在直到显式清除或应用重启。如果希望本地缓存支持过期策略,可以引入Caffeine并设置缓存规格。

三、@Cacheable的核心属性与基础示例

@Cacheable用于标记方法返回值需要缓存。它的常用属性包括cacheNamesvalue,指定缓存名称;key指定缓存键,支持SpEL表达式;conditionunless用于控制缓存行为。一个典型查询方法如下:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Cacheable(cacheNames = "user", key = "#id")
    public User getUserById(Long id) {
        // 模拟数据库查询
        System.out.println("查询数据库:" + id);
        return new User(id, "用户" + id);
    }
}

第一次调用getUserById(1)时,控制台会打印查询数据库,返回结果并存入名为user的缓存。第二次使用相同id调用时,方法体不会执行,直接返回缓存对象。需要注意,默认key是空字符串时,Spring会根据参数生成SimpleKey,不同参数组合可能不同,但建议显式指定key,避免混淆。

key表达式支持SpEL,例如#id表示参数id,#root.methodName表示方法名,还可以拼接字符串:#root.targetClass + '_' + #id。当参数是对象时,可以取属性:#user.id。不过表达式中不能调用任意方法,只能使用SpEL支持的运算符和属性访问。

conditionunless的区别:condition在方法执行前判断,如果为false则不会使用缓存,直接执行方法;unless在方法执行后判断,如果为true则不缓存返回结果。示例:

@Cacheable(cacheNames = "user", key = "#id",
        condition = "#id != null",
        unless = "#result == null")
public User getUserById(Long id) {
    return userRepository.findById(id);
}

这里#result是方法返回结果,当返回null时不缓存,避免缓存穿透。

四、缓存更新与失效:@CachePut和@CacheEvict

只读缓存用@Cacheable就够了,但数据更新时如果缓存不清除,用户会拿到旧值。Spring Cache提供@CachePut@CacheEvict@CachePut会在方法执行后把返回值放入缓存,通常用于更新方法;@CacheEvict用于删除缓存。

@CachePut示例:

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

@CacheEvict示例:

@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
    userRepository.delete(id);
}

如果要清空整个缓存区域,可以设置allEntries = true;如果希望在方法执行前先清除缓存,可以设置beforeInvocation = true,这样即使方法抛出异常也会删除缓存。

五、自定义Key生成策略与缓存配置

当多个方法使用相同缓存名称但参数不同时,key表达式可能重复。可以全局实现KeyGenerator接口,统一生成规则。示例:

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;

@Configuration
public class CacheConfig {

    @Bean
    public KeyGenerator customKeyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getSimpleName());
                sb.append(':').append(method.getName());
                for (Object param : params) {
                    sb.append(':').append(param);
                }
                return sb.toString();
            }
        };
    }
}

然后在@Cacheable上指定keyGenerator = "customKeyGenerator"。注意keykeyGenerator不能同时使用。

对于Redis缓存,默认使用JdkSerializationRedisSerializer,缓存对象需要实现Serializable接口。实际生产环境更推荐使用GenericJackson2JsonRedisSerializerStringRedisSerializer,减少存储体积并支持跨语言读取。可以通过自定义CacheManager实现:

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
}

上述代码设置了缓存过期时间为30分钟,并使用JSON序列化。这样缓存数据在Redis中可读性更强,也方便调试。

六、常见错误排查

第一个常见问题是类内部调用导致@Cacheable失效。Spring缓存依赖AOP代理,如果同一个类中的方法A直接调用方法B,方法B上的缓存注解不会生效,因为此时调用的是this对象的原始方法,没有经过代理。解决办法是将缓存方法移到独立的Service中,或者通过ApplicationContext获取代理对象再调用。

第二个常见问题是key冲突。例如两个方法都使用cacheNames = "user",且key默认使用参数id,如果参数类型相同但含义不同,可能互相覆盖。建议为每个业务方法使用独立的cacheNames,并显式定义key,例如user:info:#iduser:list:#page

第三个常见问题是在缓存null值上处理不当。@Cacheable默认情况下如果方法返回null,Redis缓存不会存储null值,但ConcurrentMapCacheManager会存储null,需要注意。可以通过unless = "#result == null"跳过null缓存,或者配置缓存管理器允许缓存null值。一般建议主动防止缓存穿透,在查询不到数据时返回空对象或使用布隆过滤器。

最后是缓存和数据库一致性。缓存必然存在延迟,更新数据库后如果忘记清除缓存,旧数据会一直存在。建议在更新方法上同时使用@CacheEvict,或者引入消息队列通知缓存失效。对于高并发场景,还可以使用缓存双删策略,即更新前后各删除一次缓存。

Spring Boot整合缓存并不复杂,关键是理解Spring Cache抽象的设计:@EnableCaching负责总开关,@Cacheable负责读取缓存,@CachePut负责更新,@CacheEvict负责删除。实际项目中建议从默认的ConcurrentMapCacheManager入手快速验证逻辑,再根据部署架构切换到Redis等分布式缓存。配置时显式指定cacheNameskey能减少很多隐性冲突,遇到注解不生效时优先检查是否发生类内部调用。

Spring Boot缓存注解EnableCaching修改时间:2026-08-27 04:41:31

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。