cocache-spring Module
The cocache-spring module bridges CoCache's core abstractions with the Spring Framework's dependency injection container. It enables declarative cache registration via @EnableCoCache, automatic Spring bean resolution for all cache components, and FactoryBean-based proxy creation.
Module Dependencies
graph LR
subgraph sg_50 ["cocache-spring Dependencies"]
core["cocache-core"]
style core fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
spring_mod["cocache-spring"]
style spring_mod fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
spring_ctx["spring-context"]
style spring_ctx fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
core --> spring_mod
spring_ctx --> spring_mod
endSource Files (10 files)
| File | Package | Purpose |
|---|---|---|
| EnableCoCache.kt | me.ahoo.cache.spring | @EnableCoCache annotation that triggers cache registration |
| EnableCoCacheRegistrar.kt | me.ahoo.cache.spring | ImportBeanDefinitionRegistrar that parses cache classes and registers beans |
| AbstractCacheFactory.kt | me.ahoo.cache.spring | Base class for Spring-aware factory pattern with bean name lookup |
| SpringCacheFactory.kt | me.ahoo.cache.spring | CacheFactory implementation using Spring ListableBeanFactory |
| CacheProxyFactoryBean.kt | me.ahoo.cache.spring.proxy | FactoryBean for standard Cache proxies |
| JoinCacheProxyFactoryBean.kt | me.ahoo.cache.spring.join | FactoryBean for JoinCache proxies |
| SpringClientSideCacheFactory.kt | me.ahoo.cache.spring.client | Resolves ClientSideCache beans or falls back to default |
| SpringKeyConverterFactory.kt | me.ahoo.cache.spring.converter | Resolves KeyConverter beans or creates ToStringKeyConverter/ExpKeyConverter |
| SpringCacheSourceFactory.kt | me.ahoo.cache.spring.source | Resolves CacheSource beans or defaults to NoOpCacheSource |
| SpringJoinKeyExtractorFactory.kt | me.ahoo.cache.spring.join | Resolves JoinKeyExtractor beans or creates ExpJoinKeyExtractor from expression |
@EnableCoCache -- Registration Entry Point
@EnableCoCache is the primary entry point:
@Import(EnableCoCacheRegistrar::class)
@Target(AnnotationTarget.CLASS)
annotation class EnableCoCache(
val caches: Array<KClass<out Cache<*, *>>> = []
)Usage:
@EnableCoCache(caches = [UserCache::class, ProductCache::class, UserProductJoinCache::class])
@Configuration
class CacheConfigurationRegistration Flow
flowchart TB
subgraph sg_51 ["EnableCoCacheRegistrar Registration Flow"]
scan["@EnableCoCache(caches=[...])<br>ImportBeanDefinitionRegistrar"]
style scan fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
parse["Parse AnnotationMetadata<br>Extract cache classes"]
style parse fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
filter1{"Is JoinCache?"}
style filter1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
co_cache["Parse as CoCacheMetadata<br>(via toCoCacheMetadata())"]
style co_cache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
join_cache["Parse as JoinCacheMetadata<br>(via toJoinCacheMetadata())"]
style join_cache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
register_metadata["Register CoCacheMetadata bean<br>(name + '.CacheMetadata')"]
style register_metadata fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
register_cache["Register CacheProxyFactoryBean<br>(name = cacheName, primary=true)"]
style register_cache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
register_join["Register JoinCacheProxyFactoryBean<br>(name = cacheName, primary=true)"]
style register_join fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
scan --> parse --> filter1
filter1 -->|No| co_cache --> register_metadata --> register_cache
filter1 -->|Yes| join_cache --> register_join
endThe registrar at EnableCoCacheRegistrar.kt:45 performs these steps:
- Extracts the
cachesarray from the@EnableCoCacheannotation attributes. - Splits cache classes into two groups: those that implement
JoinCacheand those that do not. - For non-JoinCache classes: parses
CoCacheMetadataviaKClass.toCoCacheMetadata(), registers both the metadata bean and aCacheProxyFactoryBean. - For JoinCache classes: parses
JoinCacheMetadataviaKClass.toJoinCacheMetadata(), registers aJoinCacheProxyFactoryBean.
AbstractCacheFactory Pattern
AbstractCacheFactory is the shared base class for all Spring-aware component factories. It implements a three-tier resolution strategy:
flowchart TB
subgraph sg_52 ["AbstractCacheFactory.createBean(cacheMetadata)"]
bean_name["Compute beanName<br>= cacheName + suffix"]
style bean_name fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
exists{"beanFactory<br>.containsBean<br>(beanName)?"}
style exists fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
by_name["Return beanFactory<br>.getBean(beanName)"]
style by_name fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
by_type["getBeanProvider(type)<br>.getIfAvailable()"]
style by_type fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
type_found{"Bean found<br>by type?"}
style type_found fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
fallback["Invoke fallback()<br>(default implementation)"]
style fallback fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
ttl_aware{"Is TtlConfigurationAware?"}
style ttl_aware fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
set_ttl["setTtlConfiguration(metadata)"]
style set_ttl fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
done["Return bean"]
style done fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
bean_name --> exists
exists -->|yes| by_name --> done
exists -->|no| by_type --> type_found
type_found -->|yes| ttl_aware
type_found -->|no| fallback --> ttl_aware
ttl_aware -->|yes| set_ttl --> done
ttl_aware -->|no| done
endEach subclass defines:
suffix: The convention-based bean name suffix (e.g.,".ClientSideCache",".DistributedCache",".KeyConverter",".CacheSource",".JoinKeyExtractor")getBeanType(): TheResolvableTypefor type-based bean lookupfallback(): The default factory method when no Spring bean is found
Factory Suffixes and Bean Naming
| Factory | Suffix | Example Bean Name |
|---|---|---|
SpringClientSideCacheFactory | .ClientSideCache | UserCache.ClientSideCache |
SpringKeyConverterFactory | .KeyConverter | UserCache.KeyConverter |
SpringCacheSourceFactory | .CacheSource | UserCache.CacheSource |
RedisDistributedCacheFactory (in cocache-spring-redis) | .DistributedCache | UserCache.DistributedCache |
SpringJoinKeyExtractorFactory | .JoinKeyExtractor | UserProductJoinCache.JoinKeyExtractor |
This naming convention allows users to override any component simply by declaring a Spring bean with the expected name.
CacheProxyFactoryBean
CacheProxyFactoryBean is a Spring FactoryBean that creates cache proxy instances. It lazily retrieves the CacheProxyFactory from the ApplicationContext and delegates creation:
sequenceDiagram
autonumber
participant SB as Spring BeanFactory
participant CPFB as CacheProxyFactoryBean
participant CPF as CacheProxyFactory<br>(DefaultCacheProxyFactory)
participant CCF as CoherentCacheFactory
participant EB as CacheEvictedEventBus
SB->>CPFB: getObject()
CPFB->>SB: getBean(CacheProxyFactory)
SB-->>CPFB: DefaultCacheProxyFactory
CPFB->>CPF: create(cacheMetadata)
CPF->>CPF: Generate clientId
CPF->>CPF: Create L2 (ClientSideCache)
CPF->>CPF: Create L1 (DistributedCache)
CPF->>CPF: Create L0 (CacheSource)
CPF->>CPF: Create KeyConverter
CPF->>CCF: create(CoherentCacheConfiguration)
CCF->>CCF: new DefaultCoherentCache(...)
CCF->>EB: register(coherentCache)
CCF-->>CPF: CoherentCache
CPF->>CPF: new CoCacheInvocationHandler(metadata, delegate)
CPF->>CPF: Proxy.newProxyInstance(...)
CPF-->>CPFB: Cache proxy
CPFB-->>SB: Cache proxy (as FactoryBean result)JoinCacheProxyFactoryBean
JoinCacheProxyFactoryBean follows the same pattern but retrieves the JoinCacheProxyFactory and creates a JoinCache proxy wired with the first cache, join cache, and join key extractor.
SpringCacheFactory
SpringCacheFactory implements the CacheFactory interface using Spring's ListableBeanFactory:
| Method | Strategy |
|---|---|
caches | beanFactory.getBeansOfType(Cache::class.java) |
getCache(name, type) | beanFactory.getBean(name, type) with NoSuchBeanDefinitionException handling |
getCache(keyType, valueType) | beanFactory.getBeanProvider(ResolvableType) for generic type matching |
SpringKeyConverterFactory
SpringKeyConverterFactory has special handling for String key types -- when the key type is String, it skips the bean provider lookup and goes directly to fallback(), since String keys do not need a typed converter.
The fallback logic at SpringKeyConverterFactory.kt:50:
- Resolve
keyPrefixfrom@CoCache(supports Spring property placeholders). - If no prefix, default to
"cocache:{cacheName}:". - If
keyExpressionis set, createExpKeyConverter. - Otherwise, create
ToStringKeyConverter.
SpringJoinKeyExtractorFactory
SpringJoinKeyExtractorFactory resolves join key extractors in this order:
- If
joinKeyExpressionis set in@JoinCacheable, createExpJoinKeyExtractor. - Look up a bean by name (
cacheName + ".JoinKeyExtractor"). - Look up a unique bean by type (
JoinKeyExtractor<V1, K2>). - Fail with an error if none found.
Factory Hierarchy
classDiagram
class AbstractCacheFactory {
<<abstract>>
#beanFactory: BeanFactory
+suffix: String
+createBean(cacheMetadata) Any
#getBeanName(cacheMetadata) String
#getBeanType(cacheMetadata) ResolvableType
#getBeanProvider(metadata, fallback) Any
#fallback(cacheMetadata) Any
}
class ClientSideCacheFactory {
<<interface>>
+create(cacheMetadata) ClientSideCache
}
class KeyConverterFactory {
<<interface>>
+create(cacheMetadata) KeyConverter
}
class CacheSourceFactory {
<<interface>>
+create(cacheMetadata) CacheSource
}
class SpringClientSideCacheFactory {
+suffix = ".ClientSideCache"
+fallback() DefaultClientSideCacheFactory
}
class SpringKeyConverterFactory {
+suffix = ".KeyConverter"
+fallback() ToStringKeyConverter or ExpKeyConverter
}
class SpringCacheSourceFactory {
+suffix = ".CacheSource"
+fallback() NoOpCacheSource
}
AbstractCacheFactory <|-- SpringClientSideCacheFactory
AbstractCacheFactory <|-- SpringKeyConverterFactory
AbstractCacheFactory <|-- SpringCacheSourceFactory
ClientSideCacheFactory <|.. SpringClientSideCacheFactory
KeyConverterFactory <|.. SpringKeyConverterFactory
CacheSourceFactory <|.. SpringCacheSourceFactoryJoinCache Registration Flow
sequenceDiagram
autonumber
participant User as @EnableCoCache
participant Reg as EnableCoCacheRegistrar
participant BDR as BeanDefinitionRegistry
participant App as ApplicationContext
participant JPF as JoinCacheProxyFactory
User->>Reg: registerBeanDefinitions(metadata, registry)
Reg->>Reg: Parse @EnableCoCache caches array
Reg->>Reg: Filter JoinCache subclasses
Reg->>Reg: toJoinCacheMetadata()
Reg->>BDR: Register JoinCacheProxyFactoryBean
BDR-->>Reg: Bean registered
Note over App: Later, during context refresh...
App->>App: getBean(JoinCacheProxyFactoryBean)
App->>App: getObject()
App->>JPF: create(joinCacheMetadata)
JPF->>JPF: Resolve firstCache, joinCache, joinKeyExtractor
JPF->>JPF: Create SimpleJoinCache(first, join, extractor)
JPF->>JPF: Wrap in JoinCacheProxy
JPF-->>App: JoinCache proxyCustomization Example
Users can override any component by declaring a Spring bean:
@Configuration
class CustomCacheConfig {
// Override the client-side cache for UserCache
@Bean("UserCache.ClientSideCache")
fun userCacheClientSide(): ClientSideCache<User> {
return CaffeineClientSideCache(
Caffeine.newBuilder()
.maximumSize(50_000)
.expireAfterWrite(Duration.ofMinutes(30))
.build()
)
}
// Override the cache source for UserCache
@Bean("UserCache.CacheSource")
fun userCacheSource(userRepository: UserRepository): CacheSource<String, User> {
return CacheSource { key ->
val user = userRepository.findById(key)
user.map { DefaultCacheValue.ttlAt(it, 3600) }.orElse(null)
}
}
}Related Pages
- Module Overview -- Dependency graph and module descriptions
- cocache-api -- Interfaces and annotations
- cocache-core -- Default implementations
- cocache-spring-redis -- Redis distributed cache implementation
- cocache-spring-boot-starter -- Auto-configuration
- cocache-spring-cache -- Spring Cache abstraction bridge