因工作需要使用 Spring Cache 整合 EhCache 時, 意外發現標注 @Cacheable annotation 的 method 在 same class 呼叫發揮不了效用
以下為範例 code:
@Cacheable(value = CacheConstants.FIND_ALL_TYPE) @Override public List<Type> getList() { return super.getList(); } public List<Type> getListByOrgMappingAndStatusOrderBySortAsc(OrgMapping orgMapping, Activation status) { return getList().stream() .filter(type -> type.getOrgMapping().equals(orgMapping)) .filter(type -> type.getStatus().equals(Activation.ACTIVE)) .sorted((t1, t2) -> t1.getSort().compareTo(t2.getSort())) .collect(Collectors.toList()); }<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>
折騰了很久,也發現到網路很多類似的問題:
Spring cache @Cacheable method ignored when called from within the same class
Spring Cache @Cacheable – not working while calling from another method of the same bean
@Cacheable method ignored when called from within the same class
其中讓我看到了一段文字恍然大悟!!!
Only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual cache interception at runtime even if the invoked method is marked with @Cacheable.
Wiki: Using Cacheable
原來 same class 不會觸發 Spring AOP 代理機制,必須用 AspectJ 取代 Spring AOP,以下為解決方式:
public List<Type> getListByOrgMappingOrderBySortAsc(OrgMapping orgMapping) { return getProxyManager().getList().stream() .filter(type -> type.getOrgMapping().equals(orgMapping)) .sorted(comparator) .collect(Collectors.toList()); } private TypeManager getProxyManager() { return (TypeManager) AopContext.currentProxy(); }<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>
強制透過代理物件做呼叫,這樣就會啟用了 getList 的 Cache 了。
以下為 AopContext.currentProxy() 可使用的步驟:
- Add dependency to Maven pom.xml file:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>
- Add app-config.xml file (目前 java config 標注式寫法未能啟用 expose-proxy ):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:config expose-proxy="true"/> </beans>
- import 進 AppConfig:
@ImportResource("classpath*:/app-config.xml") @EnableCaching @Configuration public class CacheConfig { @Bean @Autowired public CacheManager cacheManager(final net.sf.ehcache.CacheManager cacheManager) { cacheManager.addCache(new Cache(getDefaultConfig(CacheConstants.FIND_ALL_TYPE))); cacheManager.addCache(new Cache(getDefaultConfig(CacheConstants.FIND_ONE_TYPE))); return new EhCacheCacheManager(cacheManager); } private CacheConfiguration getDefaultConfig(String name) { return new CacheConfiguration(name, CacheConstants.maxEntriesLocalHeap) .memoryStoreEvictionPolicy(CacheConstants.memoryStoreEvictionPolicy) .eternal(CacheConstants.eternal) .timeToIdleSeconds(CacheConstants.timeToIdleSeconds) .timeToLiveSeconds(CacheConstants.timeToLiveSeconds) .maxBytesLocalDisk(CacheConstants.maxBytesLocalDisk, MemoryUnit.MEGABYTES); } }<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>
結語:
呼,整個幾乎花近半天的時間處理它…