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代理的当前对象,也就是子类的实例,这样就能正确获取到子类实现的集合名称,作为缓存的名称使用。
方法二:构造时传入集合名称到父类
如果每个子类的集合名称都是固定常量,可以在父类中添加成员变量存储集合名称,子类通过构造函数传递:
- 修改父类:
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(); }
- 子类调整:
@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来定制:
- 实现自定义解析器:
public class RepositoryCacheNameResolver implements CacheNameResolver { @Override public Collection<String> resolveCacheNames(MethodInvocationContext<?> context) { FirestoreRepository<?> repository = (FirestoreRepository<?>) context.getTarget(); return Collections.singleton(repository.getCollection()); } }
- 修改
@Cacheable注解:
@Cacheable(cacheResolver = "repositoryCacheNameResolver", key = "#id") public Optional<T> findDocumentWithId(@Nonnull String id) throws InterruptedException, ExecutionException { // 原有逻辑保持不变 }
- 将解析器注册为Spring Bean:
@Configuration public class CacheConfig { @Bean public RepositoryCacheNameResolver repositoryCacheNameResolver() { return new RepositoryCacheNameResolver(); } }
这种方式适合复杂场景,完全自定义缓存名称的生成逻辑,不受SpEL的限制。
内容的提问来源于stack exchange,提问作者quasimodo
相关产品推荐
相关产品推荐

