同じBeanの別のメソッドからキャッシュされたメソッドを呼び出すと、スプリングキャッシュは機能しません。
ここに私の問題を明確に説明する例があります。
設定:
<cache:annotation-driven cache-manager="myCacheManager" />
<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="myCache" />
</bean>
<!-- Ehcache library setup -->
<bean id="myCache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true">
<property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>
<cache name="employeeData" maxElementsInMemory="100"/>
キャッシュされたサービス:
@Named("aService")
public class AService {
@Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = getEmployeeData(date);
...
}
}
結果:
aService.getEmployeeData(someDate);
output: Cache is not being used
aService.getEmployeeData(someDate);
output:
aService.getEmployeeEnrichedData(someDate);
output: Cache is not being used
getEmployeeData
メソッド呼び出しは、2番目の呼び出しでキャッシュemployeeData
を期待どおりに使用します。ただし、getEmployeeData
クラス内(AService
内)でgetEmployeeEnrichedData
メソッドが呼び出されると、キャッシュは使用されません。
これはスプリングキャッシュがどのように機能するのですか、何か不足していますか?
これがその仕組みだと思います。私が読んだことを思い出すと、すべてのリクエストをインターセプトし、キャッシュされた値で応答するプロキシクラスが生成されますが、同じクラス内の「内部」呼び出しはキャッシュされた値を取得しません。
から https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable
プロキシを介して着信する外部メソッド呼び出しのみがインターセプトされます。つまり、呼び出されたメソッドが@Cacheableでマークされていても、実際には、ターゲットオブジェクト内のメソッドがターゲットオブジェクトの別のメソッドを呼び出す自己呼び出しは、実行時に実際のキャッシュインターセプトにつながりません。
Spring 4.3以降、この問題は self-autowiring over @Resource
アノテーションを使用して解決できました。
@Component
@CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {
/**
* 1. Self-autowired reference to proxified bean of this class.
*/
@Resource
private SphereClientFactory self;
@Override
@Cacheable(sync = true)
public SphereClient createSphereClient(@Nonnull TenantConfig tenantConfig) {
// 2. call cached method using self-bean
return self.createSphereClient(tenantConfig.getSphereClientConfig());
}
@Override
@Cacheable(sync = true)
public SphereClient createSphereClient(@Nonnull SphereClientConfig clientConfig) {
return CtpClientConfigurationUtils.createSphereClient(clientConfig);
}
}
以下の例は、同じBean内からプロキシをヒットするために使用するもので、@ mario-eisのソリューションに似ていますが、もう少し読みやすいと思います(おそらくそうではありません:-)。とにかく、サービスレベルで@Cacheableアノテーションを保持するのが好きです。
@Service
@Transactional(readOnly=true)
public class SettingServiceImpl implements SettingService {
@Inject
private SettingRepository settingRepository;
@Inject
private ApplicationContext applicationContext;
@Override
@Cacheable("settingsCache")
public String findValue(String name) {
Setting setting = settingRepository.findOne(name);
if(setting == null){
return null;
}
return setting.getValue();
}
@Override
public Boolean findBoolean(String name) {
String value = getSpringProxy().findValue(name);
if (value == null) {
return null;
}
return Boolean.valueOf(value);
}
/**
* Use proxy to hit cache
*/
private SettingService getSpringProxy() {
return applicationContext.getBean(SettingService.class);
}
...
Spring Beanでの新しいトランザクションの開始 も参照してください。
同じクラス内でメソッド呼び出しをわずかにしか使用しない小さなプロジェクトに対して私が行うことは次のとおりです。コード内のドキュメントは、同僚にとってストージに見える可能性があるため、強く推奨されます。しかし、テストが簡単で、シンプルで、迅速に実現でき、本格的なAspectJインスツルメンテーションが不要です。ただし、より頻繁に使用する場合は、AspectJソリューションをお勧めします。
@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {
private final AService _aService;
@Autowired
public AService(AService aService) {
_aService = aService;
}
@Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = _aService.getEmployeeData(date);
...
}
}
静的ウィービングを使用して、Beanの周りにプロキシを作成します。この場合、「内部」メソッドでさえ正しく動作します
私の場合、変数を追加します:
@Autowired
private AService aService;
getEmployeeData
を使用してaService
メソッドを呼び出します
@Named("aService")
public class AService {
@Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = aService.getEmployeeData(date);
...
}
}
この場合、キャッシュを使用します。
このために、内部キャッシュ(FactoryInternalCache
)と実際のキャッシュを使用します。
@Component
public class CacheableClientFactoryImpl implements ClientFactory {
private final FactoryInternalCache factoryInternalCache;
@Autowired
public CacheableClientFactoryImpl(@Nonnull FactoryInternalCache factoryInternalCache) {
this.factoryInternalCache = factoryInternalCache;
}
/**
* Returns cached client instance from cache.
*/
@Override
public Client createClient(@Nonnull AggregatedConfig aggregateConfig) {
return factoryInternalCache.createClient(aggregateConfig.getClientConfig());
}
/**
* Returns cached client instance from cache.
*/
@Override
public Client createClient(@Nonnull ClientConfig clientConfig) {
return factoryInternalCache.createClient(clientConfig);
}
/**
* Spring caching feature works over AOP proxies, thus internal calls to cached methods don't work. That's why
* this internal bean is created: it "proxifies" overloaded {@code #createClient(...)} methods
* to real AOP proxified cacheable bean method {@link #createClient}.
*
* @see <a href="https://stackoverflow.com/questions/16899604/spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-s">Spring Cache @Cacheable - not working while calling from another method of the same bean</a>
* @see <a href="https://stackoverflow.com/questions/12115996/spring-cache-cacheable-method-ignored-when-called-from-within-the-same-class">Spring cache @Cacheable method ignored when called from within the same class</a>
*/
@EnableCaching
@CacheConfig(cacheNames = "ClientFactoryCache")
static class FactoryInternalCache {
@Cacheable(sync = true)
public Client createClient(@Nonnull ClientConfig clientConfig) {
return ClientCreationUtils.createClient(clientConfig);
}
}
}