基于Alfresco 5.x的Spring 3.2事务绑定缓存技术咨询
Hey there! Let’s walk through adapting your existing transaction-bound cache to Spring 3.2’s caching abstraction, tailored to Alfresco 5.x’s dependency constraints. Your current code ties cache storage to read-only transactions via AlfrescoTransactionSupport—we can replicate this behavior using Spring’s native caching with a few custom components.
1. Understand the Core Requirement
Your existing logic only uses cache in read-only transactions, and the cache is scoped to the current transaction (cleaned up when the transaction ends). Spring 3.2’s default caches (like ConcurrentMapCache) are global, so we need to build a custom transaction-scoped cache that integrates with Alfresco’s transaction lifecycle.
2. Implement a Transaction-Bound Cache
First, create a custom Cache implementation that uses AlfrescoTransactionSupport to bind cache storage to the current read-only transaction:
import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TxnReadState; import java.util.HashMap; import java.util.Map; public class TransactionBoundCache implements Cache { private final String cacheName; public TransactionBoundCache(String cacheName) { this.cacheName = cacheName; } @Override public String getName() { return cacheName; } @Override public Object getNativeCache() { Map<String, Object> cache = getTransactionCache(); return cache != null ? cache : new HashMap<>(); } @Override public ValueWrapper get(Object key) { Map<String, Object> cache = getTransactionCache(); if (cache == null) return null; Object value = cache.get(key.toString()); return value != null ? new SimpleValueWrapper(value) : null; } @Override public <T> T get(Object key, Class<T> type) { ValueWrapper wrapper = get(key); return wrapper != null ? type.cast(wrapper.get()) : null; } @Override public void put(Object key, Object value) { Map<String, Object> cache = getTransactionCache(); if (cache != null) { cache.put(key.toString(), value); } } @Override public void evict(Object key) { Map<String, Object> cache = getTransactionCache(); if (cache != null) { cache.remove(key.toString()); } } @Override public void clear() { Map<String, Object> cache = getTransactionCache(); if (cache != null) { cache.clear(); } } // Helper to get/initialize transaction-scoped cache private Map<String, Object> getTransactionCache() { // Only enable cache for read-only transactions (matches your existing logic) if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_ONLY) { return null; } @SuppressWarnings("unchecked") Map<String, Object> cache = (Map<String, Object>) AlfrescoTransactionSupport.getResource(cacheName); if (cache == null) { cache = new HashMap<>(); AlfrescoTransactionSupport.bindResource(cacheName, cache); } return cache; } }
3. Build a Custom Cache Manager
Next, create a CacheManager to manage your transaction-bound caches:
import org.springframework.cache.Cache; import org.springframework.cache.support.AbstractCacheManager; import java.util.HashMap; import java.util.Map; public class TransactionBoundCacheManager extends AbstractCacheManager { private final Map<String, Cache> cacheMap = new HashMap<>(); @Override protected Collection<? extends Cache> loadCaches() { return cacheMap.values(); } // Add caches programmatically or via Spring config public void addCache(String cacheName) { cacheMap.put(cacheName, new TransactionBoundCache(cacheName)); afterPropertiesSet(); // Refresh the cache manager } }
4. Configure Spring to Use Your Cache Setup
XML Configuration (Alfresco 5.x typically uses XML)
Add this to your Spring context file to enable caching and register your custom components:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <!-- Enable annotation-driven caching --> <cache:annotation-driven cache-manager="transactionBoundCacheManager"/> <!-- Register custom cache manager --> <bean id="transactionBoundCacheManager" class="com.your.package.TransactionBoundCacheManager"> <property name="cacheMap"> <map> <!-- Replace "yourCachedName" with your original CACHED_NAME --> <entry key="yourCachedName" value-ref="yourCachedNameCache"/> </map> </property> </bean> <!-- Define your transaction-bound cache --> <bean id="yourCachedNameCache" class="com.your.package.TransactionBoundCache"> <constructor-arg value="yourCachedName"/> </bean> </beans>
Java Configuration (If your setup supports it)
If you use Java-based config, use this:
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager transactionBoundCacheManager() { TransactionBoundCacheManager manager = new TransactionBoundCacheManager(); manager.addCache("yourCachedName"); // Match your original CACHED_NAME return manager; } }
5. Migrate Your Business Logic to Spring Cache Annotations
Replace your manual cache handling with Spring’s @Cacheable annotation. We’ll use a SpEL condition to restrict caching to read-only transactions, matching your original logic:
Original Code (Manual Cache)
public Boolean checkPermission(String key) { if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY) { Map<String, Boolean> cache = (Map<String, Boolean>) AlfrescoTransactionSupport.getResource(CACHED_NAME); if (cache == null) { cache = new HashMap<>(); AlfrescoTransactionSupport.bindResource(CACHED_NAME, cache); } Boolean value = cache.get(key); if (value == null) { value = computePermission(key); // Your core logic cache.put(key, value); } return value; } else { return computePermission(key); } }
Migrated Code (Spring Cache)
import org.springframework.cache.annotation.Cacheable; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TxnReadState; // Cache only in read-only transactions @Cacheable( value = "yourCachedName", condition = "T(org.alfresco.repo.transaction.AlfrescoTransactionSupport).getTransactionReadState() == T(org.alfresco.repo.transaction.TxnReadState).TXN_READ_ONLY" ) public Boolean checkPermission(String key) { // Only your core logic remains—cache handling is delegated to Spring return computePermission(key); }
6. Key Notes
- Transaction Cleanup: Alfresco’s transaction manager automatically cleans up resources bound via
AlfrescoTransactionSupportwhen the transaction ends, so your cache will be scoped to the current transaction (no leftover data between transactions). - Testing: Validate that caching works only in read-only transactions, and that cache entries are not retained after the transaction completes.
- Compatibility: This setup is fully compatible with Spring 3.2 and Alfresco 5.x, as it leverages Alfresco’s existing transaction support alongside Spring’s cache abstraction.
内容的提问来源于stack exchange,提问作者Nymeria

