2

I've set up a little test project at scala-spring-cache to demonstrate using the ehcache-spring-annotations in Scala.

In order to get caching to work, aspects must be used. I've got configuration to use CGLIB or AspectJ (or one can add @Scope(proxyMode = TARGET_CLASS) to get proxies just for that bean).

With aspects for auto-proxies, caching works fine when called from outside the class. However, it fails when a method is calling inside the class:

@Cacheable(cacheName = "thingy", decoratedCacheType = SELF_POPULATING_CACHE)
def expensive() = "bob"

def internallyCalling() = expensive()

Here, calls from outside the class to expensive() will use the cache, but calls to internallyCalling() will not use the cache.

How can I fix this so that caches can be used internally (like in Java)? Is it even possible?

Community
  • 1
  • 1
fommil
  • 5,757
  • 8
  • 41
  • 81

2 Answers2

1

The docs say:

Self-Invocation

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.

BTW, I had to tweak your test project to satisfy sbt.

Here are the two stack traces (below specs2) for comparison.

Externally intercepted:

at com.github.fommil.cache.Thingy.expensive(Thingy.scala:20)
at com.github.fommil.cache.Thingy$$FastClassByCGLIB$$f58b1afb.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at com.googlecode.ehcache.annotations.resolver.ThreadLocalCacheEntryFactory.createEntry(ThreadLocalCacheEntryFactory.java:36)
at net.sf.ehcache.constructs.blocking.SelfPopulatingCache.get(SelfPopulatingCache.java:73)
at net.sf.ehcache.constructs.blocking.BlockingCache.get(BlockingCache.java:243)
at com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor.invokeSelfPopulatingCacheable(EhCacheInterceptor.java:174)
at com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor.invokeCacheable(EhCacheInterceptor.java:122)
at com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor.invoke(EhCacheInterceptor.java:81)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
at com.github.fommil.cache.Thingy$$EnhancerByCGLIB$$d7d992e.expensive(<generated>)
at com.github.fommil.cache.Thingy$$FastClassByCGLIB$$f58b1afb.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:132)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
at com.github.fommil.cache.Thingy$$EnhancerByCGLIB$$c2c26afd.expensive(<generated>)
at com.github.fommil.cache.ThingySpec$$anonfun$1$$anonfun$apply$5.apply(ThingySpec.scala:16)
at com.github.fommil.cache.ThingySpec$$anonfun$1$$anonfun$apply$5.apply(ThingySpec.scala:15)

...and internally invoked:

at com.github.fommil.cache.Thingy.expensive(Thingy.scala:20)
at com.github.fommil.cache.Thingy.internallyCalling(Thingy.scala:24)
at com.github.fommil.cache.Thingy$$FastClassByCGLIB$$f58b1afb.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:627)
at com.github.fommil.cache.Thingy$$EnhancerByCGLIB$$d7d992e.internallyCalling(<generated>)
at com.github.fommil.cache.Thingy$$FastClassByCGLIB$$f58b1afb.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:132)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
at com.github.fommil.cache.Thingy$$EnhancerByCGLIB$$c2c26afd.internallyCalling(<generated>)
at com.github.fommil.cache.ThingySpec$$anonfun$1$$anonfun$apply$6.apply(ThingySpec.scala:22)
at com.github.fommil.cache.ThingySpec$$anonfun$1$$anonfun$apply$6.apply(ThingySpec.scala:21)

Not surprisingly, something like the following works:

import org.springframework.beans.factory.{ BeanFactory, BeanFactoryAware }

@Service
@Scope(proxyMode = TARGET_CLASS)
class Thingy extends JavaLogging with BeanFactoryAware {

  var called = 0

  @Cacheable(cacheName = "thingy", decoratedCacheType = SELF_POPULATING_CACHE)
  def expensive() = {
    called += 1
    Thread.sleep(1000)
    new Throwable().printStackTrace()
    "bob"
  }

  var myProxy: Thingy = _
  var myFactory: BeanFactory = _

  def setBeanFactory(beanFactory: BeanFactory): Unit = {
    myFactory = beanFactory
  }
  def internallyCalling() = {
    if (myProxy == null) myProxy = myFactory getBean classOf[Thingy]
    myProxy.expensive()
  }
}

Just to make sure there was no local instrumentation going on, which was maybe disabled depending on the call stack, I tried it by calling out to the companion object and back, and also wrapping it in a future, but the magic is at the proxy.

som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • the docs you reference are for wrapper proxy Java beans. Using cglib/aspectj allows for internal calls to be intercepted. However, in Scala, aspects are required even for external calls but there appears to be no way (through config) to enable internal calls (of course, manually creating a proxy will work... I think factoring out the code is probably cleaner) – fommil Jul 24 '13 at 09:39
  • @fommil Sorry, thought you were asking about the proxy beans. Anyway, thx for the question. – som-snytt Jul 25 '13 at 06:48
0

This doesn't seem to be possible in Scala.

fommil
  • 5,757
  • 8
  • 41
  • 81