Spring Cache 不适用于抽象类

2022-09-04 22:52:37

我试图在抽象类中使用Spring Cache,但它不起作用,因为从我所看到的,Spring正在抽象类上搜索CacheNames。我有一个使用服务层和道层的REST API。这个想法是为每个子类使用不同的缓存名称。

我的抽象服务类如下所示:

    @Service
    @Transactional
    public abstract class AbstractService<E> {

...

    @Cacheable
    public List<E> findAll() {
        return getDao().findAll();
    }
}

抽象类的扩展将如下所示:

@Service
@CacheConfig(cacheNames = "textdocuments")
public class TextdocumentsService extends AbstractService<Textdocuments> {
...
}

因此,当我使用此代码启动应用程序时,Spring会给我以下异常:

Caused by: java.lang.IllegalStateException: No cache names could be detected on 'public java.util.List foo.bar.AbstractService.findAll()'. Make sure to set the value parameter on the annotation or declare a @CacheConfig at the class-level with the default cache name(s) to use.
    at org.springframework.cache.annotation.SpringCacheAnnotationParser.validateCacheOperation(SpringCacheAnnotationParser.java:240) ~[spring-context-4.1.6.RELEASE.jar:?]

我认为发生这种情况是因为Spring正在抽象类上搜索CacheName,尽管它是在子类上声明的。

尝试使用

 @Service
 @Transactional
 @CacheConfig
        public abstract class AbstractService<E> {
    }

导致相同的例外;用

 @Service
 @Transactional
 @CacheConfig(cacheNames = "abstractservice")
        public abstract class AbstractService<E> {
    }

没有例外,但随后Spring Cache为每个子类使用相同的缓存名称,并忽略子类上定义的缓存名称。有什么想法可以解决这个问题吗?


答案 1

这个问题在另一个问题中已经得到解决,与其说是关于抽象类,不如说是关于框架确定要使用哪个缓存的能力。

长话短说(引用Spring文档)你错过了适合你的抽象类层次结构:CacheResolver

从 Spring 4.1 开始,缓存注释的 value 属性不再是必需的,因为无论注释的内容如何,缓存解析器都可以提供此特定信息。

因此,抽象类应定义缓存解析程序,而不是直接声明缓存名称。

abstract class Repository<T> {
    // .. some methods omitted for brevity

    @Cacheable(cacheResolver = CachingConfiguration.CACHE_RESOLVER_NAME)
    public List<T> findAll() {
        return getDao().findAll();
    }
}

解析程序确定用于截获方法调用的缓存实例。一个非常幼稚的实现可以采用目标存储库bean(按名称)并将其用作缓存名称

class RuntimeCacheResolver
        extends SimpleCacheResolver {

    protected RuntimeCacheResolver(CacheManager cacheManager) {
        super(cacheManager);
    }

    @Override
    protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
        return Arrays.asList(context.getTarget().getClass().getSimpleName());
    }
}

这样的解析器需要显式配置:

@Configuration
@EnableCaching
class CachingConfiguration extends CachingConfigurerSupport {

    final static String CACHE_RESOLVER_NAME = "simpleCacheResolver";

    @Bean
    @Override
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }

    @Bean(CACHE_RESOLVER_NAME)
    public CacheResolver cacheResolver(CacheManager cacheManager) {
        return new RuntimeCacheResolver(cacheManager);
    }
}

创建了一个要点,更详细地描述了整个概念。

免責聲明

上面的代码片段仅用于演示,旨在提供方向,而不是提供完整的解决方案。上面的缓存解析器实现非常幼稚,没有考虑很多东西(如方法参数等)。我永远不会在生产环境中使用它。

Spring处理缓存的方式是通过代理,其中注释声明缓存,以及在运行时处理的命名信息。缓存通过提供给缓存解析器的运行时信息进行解析(毫不奇怪,它与经典AOP的InvocationContext有一些相似之处)。@Cacheable

public interface CacheOperationInvocationContext<O extends BasicOperation> {
    O getOperation();
    Object getTarget();
    Method getMethod();
    Object[] getArgs();
}

通过该方法可以找出代理的bean,但在现实生活中,应该考虑更多的信息,以提供可靠的缓存(如方法参数等)。getTarget()


答案 2