Sometimes it becomes necessary to cache the results of method execution. One of the possible solutions for java is described
here . Everything, in principle, is trivial: EHCache, Spring AOP to intercept calls, a little bit of code.
Consider, I think, a more elegant solution to scala.
We formulate the problem more specifically. It is required to create mixin, when adding it to the service implementation, it will be possible to add caching of the results of the execution of the methods as follows:
final val CACHE_FIRST_USER_CREATED = "firstUserCreated" <br>
def isFirstUserCreated : Boolean = cache (<br>
{userDAO.getCount>0} withKey CACHE_FIRST_USER_CREATED forever<br>
)<br>
')
Let's start with a simple DSL to define caching options. We will need a class to describe the caching parameters, containing:
- key,
- Record Lifetime
- the function itself calculates the return value.
class CacheOptions [T] (fn: =>T ) {<br>
var time = - 1 <br>
var key: String = "key" <br>
}<br>
We define the methods forever, expirationTime, withKey, the default conversion for functions to fill objects of this class, and the _execFn method, which will calculate the fn value passed by name.
class CacheOptions [T] (fn: =>T ) {<br>
var time = - 1 <br>
var key: String = "key" <br>
def _execFn : T = fn<br>
def forever = { this .time= - 1 ; this }<br>
def expirationTime (time: Int ) = { this .time=time; this }<br>
def withKey (key: String ) = { this .key=key; this }<br>
}<br>
<br>
implicit def fn2co [T] (fn: =>T ) = new CacheOptions [T] (fn)<br>
<br>
Thus, the expression of the form:
{ "lazy value" } withKey "mykey" expirationTime 30000<br>
Will return a CacheOptions [String] object with the time, key, and fn fields filled in.
Let's go directly to the caching. In the article I mentioned at the beginning, EHCache is used to cache results. Nothing prevents you from doing the same, however, for simplicity, I will show the caching of results in a simple ConcurrentHashMap.
So, the actual cache method:
private val cache = new ConcurrentHashMap [String,CacheRecord] <br>
<br>
def cache [T] (co: CacheOptions[T] ): T = {<br>
val timestamp = System.currentTimeMillis()<br>
val cr = cache.get(co.key)<br>
(cr== null ) match {<br>
case true => refresh(timestamp,co)<br>
case false => <br>
if (co.time> 0 && (timestamp-cr.timestamp)>co.time) <br>
refresh(timestamp,co) <br>
else <br>
cr.obj.asInstanceOf[ T ]<br>
}<br>
}<br>
<br>
private def refresh [T] (timestamp: Long , co: CacheOptions[T] ): T = {<br>
val cr = new CacheRecord (timestamp, co._execFn.asInstanceOf[ AnyRef ])<br>
cache.put(co.key, cr)<br>
cr.obj.asInstanceOf[ T ]<br>
}<br>
<br>
No space technology - try to select from the cache, check the timestamp of the record, execute and cache, if necessary.
Add a simple method to force the deletion of entries from the cache:
def evict (key: String ): Unit = cache.remove(key)<br>
Done!
Minus, in comparison with the use of spring aop - it is necessary to describe the procedure for creating a key, depending on the input parameters (in the spring aop solution this happens automatically). Pros - simplicity, the ability to forcibly delete records.
For example, I showed caching in ConcurrentHashMap, the disadvantages of such a solution, compared to using EHCache, are obvious - there is no possibility to limit the number of entries in the cache and determine the strategy for deleting entries, you must independently ensure that the returned values ​​are immutable, otherwise there will be the possibility to change the values cached But all these problems can be avoided by “switching” this trait to use EHCache or any other library. This is a matter of 5 minutes.