1
1
/*
2
- * Copyright 2011-2015 the original author or authors.
2
+ * Copyright 2011-2016 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
19
19
import static org .springframework .util .Assert .*;
20
20
import static org .springframework .util .ObjectUtils .*;
21
21
22
+ import java .lang .reflect .Constructor ;
22
23
import java .util .Arrays ;
23
24
import java .util .Set ;
25
+ import java .util .concurrent .Callable ;
24
26
25
27
import org .springframework .cache .Cache ;
26
28
import org .springframework .cache .support .SimpleValueWrapper ;
27
29
import org .springframework .dao .DataAccessException ;
30
+ import org .springframework .data .redis .RedisSystemException ;
28
31
import org .springframework .data .redis .connection .RedisConnection ;
29
32
import org .springframework .data .redis .connection .ReturnType ;
30
33
import org .springframework .data .redis .core .RedisCallback ;
31
34
import org .springframework .data .redis .core .RedisOperations ;
32
35
import org .springframework .data .redis .serializer .RedisSerializer ;
33
36
import org .springframework .data .redis .serializer .StringRedisSerializer ;
37
+ import org .springframework .util .ClassUtils ;
34
38
35
39
/**
36
40
* Cache implementation on top of Redis.
@@ -91,6 +95,32 @@ public ValueWrapper get(Object key) {
91
95
redisOperations .getKeySerializer ()));
92
96
}
93
97
98
+ /*
99
+ * @see org.springframework.cache.Cache#get(java.lang.Object, java.util.concurrent.Callable)
100
+ * introduced in springframework 4.3.0.RC1
101
+ */
102
+ public <T > T get (final Object key , final Callable <T > valueLoader ) {
103
+
104
+ BinaryRedisCacheElement rce = new BinaryRedisCacheElement (new RedisCacheElement (new RedisCacheKey (key ).usePrefix (
105
+ cacheMetadata .getKeyPrefix ()).withKeySerializer (redisOperations .getKeySerializer ()), valueLoader ),
106
+ cacheValueAccessor );
107
+
108
+ ValueWrapper val = get (key );
109
+ if (val != null ) {
110
+ return (T ) val .get ();
111
+ }
112
+
113
+ RedisWriteThroughCallback callback = new RedisWriteThroughCallback (rce , cacheMetadata );
114
+
115
+ try {
116
+ byte [] result = (byte []) redisOperations .execute (callback );
117
+ return (T ) (result == null ? null : cacheValueAccessor .deserializeIfNecessary (result ));
118
+ } catch (RuntimeException e ) {
119
+ throw CacheValueRetrievalExceptionFactory .INSTANCE .create (key , valueLoader , e );
120
+ }
121
+
122
+ }
123
+
94
124
/**
95
125
* Return the value to which this cache maps the specified key.
96
126
*
@@ -361,13 +391,18 @@ static class BinaryRedisCacheElement extends RedisCacheElement {
361
391
private byte [] keyBytes ;
362
392
private byte [] valueBytes ;
363
393
private RedisCacheElement element ;
394
+ private boolean lazyLoad ;
395
+ private CacheValueAccessor accessor ;
364
396
365
397
public BinaryRedisCacheElement (RedisCacheElement element , CacheValueAccessor accessor ) {
366
398
367
399
super (element .getKey (), element .get ());
368
400
this .element = element ;
369
401
this .keyBytes = element .getKeyBytes ();
370
- this .valueBytes = accessor .convertToBytesIfNecessary (element .get ());
402
+ this .accessor = accessor ;
403
+
404
+ lazyLoad = element .get () instanceof Callable ;
405
+ this .valueBytes = lazyLoad ? null : accessor .convertToBytesIfNecessary (element .get ());
371
406
}
372
407
373
408
@ Override
@@ -393,9 +428,16 @@ public RedisCacheElement expireAfter(long seconds) {
393
428
394
429
@ Override
395
430
public byte [] get () {
431
+
432
+ if (lazyLoad && valueBytes == null ) {
433
+ try {
434
+ valueBytes = accessor .convertToBytesIfNecessary (((Callable <?>) element .get ()).call ());
435
+ } catch (Exception e ) {
436
+ throw e instanceof RuntimeException ? (RuntimeException ) e : new RuntimeException (e .getMessage (), e );
437
+ }
438
+ }
396
439
return valueBytes ;
397
440
}
398
-
399
441
}
400
442
401
443
/**
@@ -470,6 +512,15 @@ protected boolean waitForLock(RedisConnection connection) {
470
512
471
513
return foundLock ;
472
514
}
515
+
516
+ protected void lock (RedisConnection connection ) {
517
+ waitForLock (connection );
518
+ connection .set (cacheMetadata .getCacheLockKey (), "locked" .getBytes ());
519
+ }
520
+
521
+ protected void unlock (RedisConnection connection ) {
522
+ connection .del (cacheMetadata .getCacheLockKey ());
523
+ }
473
524
}
474
525
475
526
/**
@@ -666,4 +717,86 @@ private byte[] put(BinaryRedisCacheElement element, RedisConnection connection)
666
717
}
667
718
}
668
719
720
+ /**
721
+ * @author Christoph Strobl
722
+ * @since 1.7
723
+ */
724
+ static class RedisWriteThroughCallback extends AbstractRedisCacheCallback <byte []> {
725
+
726
+ public RedisWriteThroughCallback (BinaryRedisCacheElement element , RedisCacheMetadata metadata ) {
727
+ super (element , metadata );
728
+ }
729
+
730
+ @ Override
731
+ public byte [] doInRedis (BinaryRedisCacheElement element , RedisConnection connection ) throws DataAccessException {
732
+
733
+ try {
734
+
735
+ lock (connection );
736
+
737
+ try {
738
+
739
+ byte [] value = connection .get (element .getKeyBytes ());
740
+
741
+ if (value != null ) {
742
+ return value ;
743
+ }
744
+
745
+ connection .watch (element .getKeyBytes ());
746
+ connection .multi ();
747
+
748
+ value = element .get ();
749
+ connection .set (element .getKeyBytes (), value );
750
+
751
+ processKeyExpiration (element , connection );
752
+ maintainKnownKeys (element , connection );
753
+
754
+ connection .exec ();
755
+
756
+ return value ;
757
+ } catch (RuntimeException e ) {
758
+
759
+ connection .discard ();
760
+ throw e ;
761
+ }
762
+ } finally {
763
+ unlock (connection );
764
+ }
765
+ }
766
+ };
767
+
768
+ /**
769
+ * @author Christoph Strobl
770
+ * @since 1.7 (TODO: remove when upgrading to spring 4.3)
771
+ */
772
+ private static enum CacheValueRetrievalExceptionFactory {
773
+
774
+ INSTANCE ;
775
+
776
+ private static boolean isSpring43 ;
777
+
778
+ static {
779
+ isSpring43 = ClassUtils .isPresent ("org.springframework.cache.Cache$ValueRetrievalException" ,
780
+ ClassUtils .getDefaultClassLoader ());
781
+ }
782
+
783
+ public RuntimeException create (Object key , Callable <?> valueLoader , Throwable cause ) {
784
+
785
+ if (isSpring43 ) {
786
+ try {
787
+ Class <?> execption = ClassUtils .forName ("org.springframework.cache.Cache$ValueRetrievalException" , this
788
+ .getClass ().getClassLoader ());
789
+ Constructor <?> c = ClassUtils .getConstructorIfAvailable (execption , Object .class , Callable .class ,
790
+ Throwable .class );
791
+ return (RuntimeException ) c .newInstance (key , valueLoader , cause );
792
+ } catch (Exception ex ) {
793
+ // ignore
794
+ }
795
+ }
796
+
797
+ return new RedisSystemException (String .format ("Value for key '%s' could not be loaded using '%s'." , key ,
798
+ valueLoader ), cause );
799
+ }
800
+ }
801
+
669
802
}
0 commit comments