21
21
import java .util .Map ;
22
22
import java .util .Map .Entry ;
23
23
import java .util .Set ;
24
+ import java .util .concurrent .atomic .AtomicReference ;
24
25
25
26
import org .slf4j .Logger ;
26
27
import org .slf4j .LoggerFactory ;
27
28
import org .springframework .beans .BeansException ;
29
+ import org .springframework .beans .factory .DisposableBean ;
30
+ import org .springframework .beans .factory .InitializingBean ;
28
31
import org .springframework .context .ApplicationContext ;
29
32
import org .springframework .context .ApplicationContextAware ;
33
+ import org .springframework .context .ApplicationEventPublisher ;
30
34
import org .springframework .context .ApplicationListener ;
31
35
import org .springframework .core .convert .ConversionService ;
32
36
import org .springframework .core .convert .ConverterNotFoundException ;
50
54
import org .springframework .data .redis .util .ByteUtils ;
51
55
import org .springframework .data .util .CloseableIterator ;
52
56
import org .springframework .util .Assert ;
57
+ import org .springframework .util .ObjectUtils ;
53
58
54
59
/**
55
60
* Redis specific {@link KeyValueAdapter} implementation. Uses binary codec to read/write data from/to Redis. Objects
56
61
* are stored in a Redis Hash using the value of {@link RedisHash}, the {@link KeyspaceConfiguration} or just
57
62
* {@link Class#getName()} as a prefix. <br />
58
63
* <strong>Example</strong>
59
- *
64
+ *
60
65
* <pre>
61
66
* <code>
62
67
* @RedisHash("persons")
63
68
* class Person {
64
69
* @Id String id;
65
70
* String name;
66
71
* }
67
- *
68
- *
72
+ *
73
+ *
69
74
* prefix ID
70
75
* | |
71
76
* V V
76
81
* 4) Rand al'Thor
77
82
* </code>
78
83
* </pre>
79
- *
84
+ *
80
85
* <br />
81
86
* The {@link KeyValueAdapter} is <strong>not</strong> intended to store simple types such as {@link String} values.
82
87
* Please use {@link RedisTemplate} for this purpose.
83
- *
88
+ *
84
89
* @author Christoph Strobl
85
90
* @author Mark Paluch
86
91
* @since 1.7
87
92
*/
88
93
public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
89
- implements ApplicationContextAware , ApplicationListener <RedisKeyspaceEvent > {
94
+ implements InitializingBean , ApplicationContextAware , ApplicationListener <RedisKeyspaceEvent > {
90
95
91
96
private static final Logger LOGGER = LoggerFactory .getLogger (RedisKeyValueAdapter .class );
92
97
93
98
private RedisOperations <?, ?> redisOps ;
94
99
private RedisConverter converter ;
95
100
private RedisMessageListenerContainer messageListenerContainer ;
96
- private KeyExpirationEventMessageListener expirationListener ;
101
+ private AtomicReference <KeyExpirationEventMessageListener > expirationListener = new AtomicReference <KeyExpirationEventMessageListener >(
102
+ null );
103
+ private ApplicationEventPublisher eventPublisher ;
104
+
105
+ private EnableKeyspaceEvents enableKeyspaceEvents = EnableKeyspaceEvents .ON_STARTUP ;
97
106
98
107
/**
99
108
* Creates new {@link RedisKeyValueAdapter} with default {@link RedisMappingContext} and default
100
109
* {@link CustomConversions}.
101
- *
110
+ *
102
111
* @param redisOps must not be {@literal null}.
103
112
*/
104
113
public RedisKeyValueAdapter (RedisOperations <?, ?> redisOps ) {
@@ -107,7 +116,7 @@ public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps) {
107
116
108
117
/**
109
118
* Creates new {@link RedisKeyValueAdapter} with default {@link CustomConversions}.
110
- *
119
+ *
111
120
* @param redisOps must not be {@literal null}.
112
121
* @param mappingContext must not be {@literal null}.
113
122
*/
@@ -117,7 +126,7 @@ public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisMappingContext
117
126
118
127
/**
119
128
* Creates new {@link RedisKeyValueAdapter}.
120
- *
129
+ *
121
130
* @param redisOps must not be {@literal null}.
122
131
* @param mappingContext must not be {@literal null}.
123
132
* @param customConversions can be {@literal null}.
@@ -138,12 +147,12 @@ public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisMappingContext
138
147
converter = mappingConverter ;
139
148
this .redisOps = redisOps ;
140
149
141
- initKeyExpirationListener ();
150
+ intiMessageListenerContainer ();
142
151
}
143
152
144
153
/**
145
154
* Creates new {@link RedisKeyValueAdapter} with specific {@link RedisConverter}.
146
- *
155
+ *
147
156
* @param redisOps must not be {@literal null}.
148
157
* @param mappingContext must not be {@literal null}.
149
158
*/
@@ -156,7 +165,7 @@ public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisConverter redis
156
165
converter = redisConverter ;
157
166
this .redisOps = redisOps ;
158
167
159
- initKeyExpirationListener ();
168
+ intiMessageListenerContainer ();
160
169
}
161
170
162
171
/**
@@ -175,6 +184,14 @@ public Object put(final Serializable id, final Object item, final Serializable k
175
184
converter .write (item , rdo );
176
185
}
177
186
187
+ if (ObjectUtils .nullSafeEquals (EnableKeyspaceEvents .ON_DEMAND , enableKeyspaceEvents )
188
+ && this .expirationListener .get () == null ) {
189
+
190
+ if (rdo .getTimeToLive () != null && rdo .getTimeToLive ().longValue () > 0 ) {
191
+ initKeyExpirationListener ();
192
+ }
193
+ }
194
+
178
195
if (rdo .getId () == null ) {
179
196
180
197
rdo .setId (converter .getConversionService ().convert (id , String .class ));
@@ -397,7 +414,7 @@ public Long doInRedis(RedisConnection connection) throws DataAccessException {
397
414
398
415
/**
399
416
* Execute {@link RedisCallback} via underlying {@link RedisOperations}.
400
- *
417
+ *
401
418
* @param callback must not be {@literal null}.
402
419
* @see RedisOperations#execute(RedisCallback)
403
420
* @return
@@ -408,7 +425,7 @@ public <T> T execute(RedisCallback<T> callback) {
408
425
409
426
/**
410
427
* Get the {@link RedisConverter} in use.
411
- *
428
+ *
412
429
* @return never {@literal null}.
413
430
*/
414
431
public RedisConverter getConverter () {
@@ -430,7 +447,7 @@ public byte[] createKey(String keyspace, String id) {
430
447
431
448
/**
432
449
* Convert given source to binary representation using the underlying {@link ConversionService}.
433
- *
450
+ *
434
451
* @param source
435
452
* @return
436
453
* @throws ConverterNotFoundException
@@ -444,13 +461,38 @@ public byte[] toBytes(Object source) {
444
461
return converter .getConversionService ().convert (source , byte [].class );
445
462
}
446
463
464
+ /**
465
+ * Configure usage of {@link KeyExpirationEventMessageListener}.
466
+ *
467
+ * @param enableKeyspaceEvents
468
+ * @since 1.8
469
+ */
470
+ public void setEnableKeyspaceEvents (EnableKeyspaceEvents enableKeyspaceEvents ) {
471
+ this .enableKeyspaceEvents = enableKeyspaceEvents ;
472
+ }
473
+
474
+ /**
475
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
476
+ * @since 1.8
477
+ */
478
+ @ Override
479
+ public void afterPropertiesSet () {
480
+
481
+ if (ObjectUtils .nullSafeEquals (EnableKeyspaceEvents .ON_STARTUP , this .enableKeyspaceEvents )) {
482
+ initKeyExpirationListener ();
483
+ }
484
+ }
485
+
447
486
/*
448
487
* (non-Javadoc)
449
488
* @see org.springframework.beans.factory.DisposableBean#destroy()
450
489
*/
451
490
public void destroy () throws Exception {
452
491
453
- this .expirationListener .destroy ();
492
+ if (this .expirationListener .get () != null ) {
493
+ this .expirationListener .get ().destroy ();
494
+ }
495
+
454
496
this .messageListenerContainer .destroy ();
455
497
}
456
498
@@ -490,26 +532,39 @@ public Void doInRedis(RedisConnection connection) throws DataAccessException {
490
532
*/
491
533
@ Override
492
534
public void setApplicationContext (ApplicationContext applicationContext ) throws BeansException {
493
- this .expirationListener . setApplicationEventPublisher ( applicationContext ) ;
535
+ this .eventPublisher = applicationContext ;
494
536
}
495
537
496
- private void initKeyExpirationListener () {
538
+ private void intiMessageListenerContainer () {
497
539
498
540
this .messageListenerContainer = new RedisMessageListenerContainer ();
499
541
messageListenerContainer .setConnectionFactory (((RedisTemplate <?, ?>) redisOps ).getConnectionFactory ());
500
542
messageListenerContainer .afterPropertiesSet ();
501
543
messageListenerContainer .start ();
544
+ }
545
+
546
+ private void initKeyExpirationListener () {
547
+
548
+ if (this .expirationListener .get () == null ) {
549
+
550
+ MappingExpirationListener listener = new MappingExpirationListener (this .messageListenerContainer , this .redisOps ,
551
+ this .converter );
552
+
553
+ if (this .eventPublisher != null ) {
554
+ listener .setApplicationEventPublisher (this .eventPublisher );
555
+ }
502
556
503
- this .expirationListener = new MappingExpirationListener (this .messageListenerContainer , this .redisOps ,
504
- this .converter );
505
- this .expirationListener .init ();
557
+ if (this .expirationListener .compareAndSet (null , listener )) {
558
+ listener .init ();
559
+ }
560
+ }
506
561
}
507
562
508
563
/**
509
564
* {@link MessageListener} implementation used to capture Redis keypspace notifications. Tries to read a previously
510
565
* created phantom key {@code keyspace:id:phantom} to provide the expired object as part of the published
511
566
* {@link RedisKeyExpiredEvent}.
512
- *
567
+ *
513
568
* @author Christoph Strobl
514
569
* @since 1.7
515
570
*/
@@ -520,7 +575,7 @@ static class MappingExpirationListener extends KeyExpirationEventMessageListener
520
575
521
576
/**
522
577
* Creates new {@link MappingExpirationListener}.
523
- *
578
+ *
524
579
* @param listenerContainer
525
580
* @param ops
526
581
* @param converter
@@ -582,4 +637,26 @@ private boolean isKeyExpirationMessage(Message message) {
582
637
}
583
638
}
584
639
640
+ /**
641
+ * @author Christoph Strobl
642
+ * @since 1.8
643
+ */
644
+ public static enum EnableKeyspaceEvents {
645
+
646
+ /**
647
+ * Initializes the {@link KeyExpirationEventMessageListener} on startup.
648
+ */
649
+ ON_STARTUP ,
650
+
651
+ /**
652
+ * Initializes the {@link KeyExpirationEventMessageListener} on first insert having expiration time set.
653
+ */
654
+ ON_DEMAND ,
655
+
656
+ /**
657
+ * Turn {@link KeyExpirationEventMessageListener} usage off. No expiration events will be received.
658
+ */
659
+ OFF
660
+ }
661
+
585
662
}
0 commit comments