You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Spring中如何在@Cacheable注解的value属性中调用方法

问题描述

我有如下抽象仓库类:

public abstract class FirestoreRepository<T> {
    protected Firestore db;

    protected FirestoreRepository(Firestore db) {
        this.db = db;
    }

    @Cacheable(value = , key = "#id")
    public Optional<T> findDocumentWithId(@Nonnull String id) throws InterruptedException, ExecutionException {
        DocumentSnapshot doc = db.collection(getCollection()).document(id).get().get();
        return Optional.ofNullable(doc.exists() ? doc.toObject(getEntityClass()) : null);
    }

    public abstract @Nonnull String getCollection();


...
}

还有多个继承自该类的仓库实现类,例如:

@Service
public class UserRepository extends FirestoreRepository<User> {

    @Autowired
    public UserRepository(Firestore db) {
        super(db);
    }
    
    @Override
    public @Nonnull String getCollection() {
        return FirestoreConstants.USERS_COLLECTIONS_NAME;
    }


...
}

我希望在@Cacheable注解的value属性中调用getCollection()方法,类似这样的写法:@Cacheable(value = "#getCollection()", key = "#id"),请问该如何实现?

实现方案

直接写#getCollection()在@Cacheable的value里是行不通的,因为Spring表达式语言(SpEL)处理注解属性时,默认没法直接调用当前类的非静态方法(尤其是抽象方法的子类实现)。下面提供几种可行的解决方式:

方法一:用SpEL的target引用当前实例

修改@Cacheable注解,通过target.getCollection()调用当前子类实例的getCollection()方法:

@Cacheable(value = "#target.getCollection()", key = "#id")
public Optional<T> findDocumentWithId(@Nonnull String id) throws InterruptedException, ExecutionException {
    // 原有逻辑保持不变
}

target在SpEL中代表被Spring代理的当前对象,也就是子类的实例,这样就能正确获取到子类实现的集合名称,作为缓存的名称使用。

方法二:构造时传入集合名称到父类

如果每个子类的集合名称都是固定常量,可以在父类中添加成员变量存储集合名称,子类通过构造函数传递:

  1. 修改父类:
public abstract class FirestoreRepository<T> {
    protected Firestore db;
    protected final String collectionName;

    protected FirestoreRepository(Firestore db, String collectionName) {
        this.db = db;
        this.collectionName = collectionName;
    }

    @Cacheable(value = "#collectionName", key = "#id")
    public Optional<T> findDocumentWithId(@Nonnull String id) throws InterruptedException, ExecutionException {
        DocumentSnapshot doc = db.collection(collectionName).document(id).get().get();
        return Optional.ofNullable(doc.exists() ? doc.toObject(getEntityClass()) : null);
    }

    public abstract @Nonnull Class<T> getEntityClass();
}
  1. 子类调整:
@Service
public class UserRepository extends FirestoreRepository<User> {

    @Autowired
    public UserRepository(Firestore db) {
        super(db, FirestoreConstants.USERS_COLLECTIONS_NAME);
    }
    
    @Override
    public @Nonnull Class<User> getEntityClass() {
        return User.class;
    }
}

这种方式通过构造函数把集合名称注入父类,SpEL直接引用父类的collectionName成员变量作为缓存名,逻辑更直观,也避免了方法调用的问题。

方法三:自定义缓存名称解析器

如果需要更灵活的缓存名称生成逻辑,可以实现CacheNameResolver来定制:

  1. 实现自定义解析器:
public class RepositoryCacheNameResolver implements CacheNameResolver {
    @Override
    public Collection<String> resolveCacheNames(MethodInvocationContext<?> context) {
        FirestoreRepository<?> repository = (FirestoreRepository<?>) context.getTarget();
        return Collections.singleton(repository.getCollection());
    }
}
  1. 修改@Cacheable注解:
@Cacheable(cacheResolver = "repositoryCacheNameResolver", key = "#id")
public Optional<T> findDocumentWithId(@Nonnull String id) throws InterruptedException, ExecutionException {
    // 原有逻辑保持不变
}
  1. 将解析器注册为Spring Bean:
@Configuration
public class CacheConfig {
    @Bean
    public RepositoryCacheNameResolver repositoryCacheNameResolver() {
        return new RepositoryCacheNameResolver();
    }
}

这种方式适合复杂场景,完全自定义缓存名称的生成逻辑,不受SpEL的限制。

内容的提问来源于stack exchange,提问作者quasimodo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 17:17:46