Skip to content

Commit 6cf7ec3

Browse files
committed
Refine configuration and annotation model.
Make RedisListener repeatable and accept a single topic instead of a pattern and channel mix. Introduce TopicResolver for configurable resolution of the topic (channel or pattern), align configuration infrastructure with JMS endpoint config model. Refine bean methods by adding a distinct bean name, introduce PubSubHeaders for well-known header names. Extend Javadoc. Add tests for converted payload and header value injection. Register converter for byte array to String conversion.
1 parent db7c918 commit 6cf7ec3

18 files changed

+994
-348
lines changed

pom.xml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@
9191
<artifactId>spring-context-support</artifactId>
9292
</dependency>
9393

94+
<!-- Messaging for @EnableRedisListeners -->
95+
<dependency>
96+
<groupId>org.springframework</groupId>
97+
<artifactId>spring-messaging</artifactId>
98+
<optional>true</optional>
99+
</dependency>
100+
94101
<!-- Redis Drivers -->
95102

96103
<dependency>
@@ -222,13 +229,6 @@
222229
<scope>test</scope>
223230
</dependency>
224231

225-
<!-- Messaging -->
226-
<dependency>
227-
<groupId>org.springframework</groupId>
228-
<artifactId>spring-messaging</artifactId>
229-
<optional>true</optional>
230-
</dependency>
231-
232232
<!-- CDI -->
233233
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
234234

src/main/java/org/springframework/data/redis/annotation/EnableRedisListeners.java

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,113 @@
1515
*/
1616
package org.springframework.data.redis.annotation;
1717

18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
1824
import org.springframework.context.annotation.Import;
1925
import org.springframework.data.redis.config.RedisListenerBootstrapConfiguration;
20-
21-
import java.lang.annotation.*;
26+
import org.springframework.data.redis.config.RedisListenerEndpointRegistry;
2227

2328
/**
24-
* Enable Redis listener annotated endpoints that are created under the cover by a
25-
* {@link org.springframework.data.redis.config.RedisListenerBootstrapConfiguration}.
29+
* Enable Redis Pub/Sub listener annotated endpoints that are created under the cover by a
30+
* {@link RedisListenerAnnotationBeanPostProcessor}. To be used on
31+
* {@link org.springframework.context.annotation.Configuration @Configuration} classes as follows:
32+
*
33+
* <pre class="code">
34+
* &#064;Configuration
35+
* &#064;EnableRedisListeners
36+
* public class AppConfig {
37+
*
38+
* &#064;Bean
39+
* public RedisMessageListenerContainer myRedisListenerContainer() {
40+
* RedisMessageListenerContainer factory = new RedisMessageListenerContainer();
41+
* factory.setConnectionFactory(connectionFactory());
42+
* return factory;
43+
* }
44+
*
45+
* // other &#064;Bean definitions
46+
* }
47+
* </pre>
48+
* <p>
49+
* The {@code RedisListenerAnnotationBeanPostProcessor} is responsible for creating endpoints.
50+
* <p>
51+
* {@code @EnableRedisListeners} enables detection of {@link RedisListener @RedisListener} annotations on any
52+
* Spring-managed bean in the container. For example, given a class {@code MyService}:
53+
*
54+
* <pre class="code">
55+
* package com.acme.foo;
56+
*
57+
* public class MyService {
58+
*
59+
* &#064;RedisListener(container = "myRedisListenerContainer", topic = "myChannel")
60+
* public void process(String msg) {
61+
* // process incoming message
62+
* }
63+
* }
64+
* </pre>
65+
* <p>
66+
* The container to use is identified by the {@link RedisListener#container() container} attribute defining the name of
67+
* the {@code RedisMessageListenerContainer} bean to use. When none is set a default
68+
* {@code RedisMessageListenerContainer} bean is assumed to be present.
69+
* <p>
70+
* The following configuration would ensure that every time a Pub/Sub is received on the topic channel named
71+
* "myChannel", {@code MyService.process()} is invoked with the content of the message:
72+
*
73+
* <pre class="code">
74+
* &#064;Configuration
75+
* &#064;EnableRedisListeners
76+
* public class AppConfig {
77+
*
78+
* &#064;Bean
79+
* public MyService myService() {
80+
* return new MyService();
81+
* }
82+
*
83+
* // Redis infrastructure setup
84+
* }
85+
* </pre>
86+
* <p>
87+
* Alternatively, if {@code MyService} were annotated with {@code @Component}, the following configuration would ensure
88+
* that its {@code @EnableRedisListeners} annotated method is invoked with a matching incoming message:
89+
*
90+
* <pre class="code">
91+
* &#064;Configuration
92+
* &#064;EnableRedisListeners
93+
* &#064;ComponentScan(basePackages = "com.acme.foo")
94+
* public class AppConfig {}
95+
* </pre>
96+
* <p>
97+
* Annotated methods can use flexible signature; in particular, it is possible to use the
98+
* {@link org.springframework.messaging.Message Message} abstraction and related annotations, see {@link RedisListener}
99+
* Javadoc for more details. For instance, the following would inject the content of the message and the "channel" name
100+
* header:
101+
*
102+
* <pre class="code">
103+
* &#064;RedisListener(container = "myRedisListenerContainer", topic = "myChannel")
104+
* public void process(String msg, @Header("channel") String channel) {
105+
* // process incoming message
106+
* }
107+
* </pre>
108+
* <p>
109+
* These features are abstracted by the
110+
* {@link org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory} that is responsible for
111+
* building the necessary invoker to process the annotated method. By default,
112+
* {@link org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory} is used.
113+
* <p>
26114
*
27115
* @author Ilyass Bougati
116+
* @since 4.1
28117
* @see RedisListener
29118
* @see RedisListenerAnnotationBeanPostProcessor
119+
* @see RedisListenerEndpointRegistry
30120
*/
31121
@Target(ElementType.TYPE)
32122
@Retention(RetentionPolicy.RUNTIME)
33123
@Documented
34124
@Import(RedisListenerBootstrapConfiguration.class)
35125
public @interface EnableRedisListeners {
36-
// Standard enable annotation attributes can be added here
126+
37127
}

src/main/java/org/springframework/data/redis/annotation/RedisListener.java

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,77 @@
1515
*/
1616
package org.springframework.data.redis.annotation;
1717

18-
import java.lang.annotation.*;
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Repeatable;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.messaging.handler.annotation.MessageMapping;
1926

2027
/**
21-
* Annotation that marks a method to be the target of a Redis message listener on the specified {@link #channels() topics}
22-
* (or {@link #patterns() patterns}).
28+
* Annotation that marks a method to be the target of a Redis Pub/Sub message listener on the specified {@link #topic}.
29+
* The {@link #container()} identifies the {@link org.springframework.data.redis.listener.RedisMessageListenerContainer}
30+
* to subscribe with. If not set, a <em>default</em> container is assumed to be available.
31+
* <p>
32+
* Processing of {@code @RedisListener} annotations is performed by registering a
33+
* {@link RedisListenerAnnotationBeanPostProcessor}. This can be done manually or, more conveniently, through the
34+
* {@link EnableRedisListeners @EnableRedisListeners} annotation.
35+
* <p>
36+
* Annotated Redis listener methods are allowed to have flexible signatures similar to what {@link MessageMapping}
37+
* provides:
38+
* <ul>
39+
* <li>{@link org.springframework.data.redis.listener.Topic} to get access to the Redis topic</li>
40+
* <li>{@link org.springframework.messaging.Message} to use Spring's messaging abstraction</li>
41+
* <li>{@link org.springframework.messaging.handler.annotation.Payload @Payload}-annotated method arguments, including
42+
* support for validation</li>
43+
* <li>{@link org.springframework.messaging.handler.annotation.Header @Header}-annotated method arguments to extract
44+
* specific header values, including Redis headers defined by
45+
* {@link org.springframework.data.redis.listener.support.PubSubHeaders}</li>
46+
* <li>{@link org.springframework.messaging.handler.annotation.Headers @Headers}-annotated method argument that must
47+
* also be assignable to {@link java.util.Map} for obtaining access to all headers</li>
48+
* <li>{@link org.springframework.messaging.MessageHeaders} arguments for obtaining access to all headers</li>
49+
* <li>{@link org.springframework.messaging.support.MessageHeaderAccessor} for convenient access to all method
50+
* arguments</li>
51+
* </ul>
52+
* <p>
53+
* This annotation can be used as a <em>{@linkplain Repeatable repeatable}</em> annotation.
54+
* <p>
55+
* This annotation may be used as a <em>meta-annotation</em> to create custom <em>composed annotations</em> with
56+
* attribute overrides.
2357
*
2458
* @author Ilyass Bougati
59+
* @author Mark Paluch
60+
* @since 4.1
2561
* @see EnableRedisListeners
62+
* @see RedisListenerAnnotationBeanPostProcessor
63+
* @see RedisListeners
2664
*/
2765
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
2866
@Retention(RetentionPolicy.RUNTIME)
2967
@Documented
68+
@Repeatable(RedisListeners.class)
69+
@MessageMapping
3070
public @interface RedisListener {
3171

3272
/**
3373
* The unique identifier of the container managing this endpoint.
74+
* <p>
75+
* If none is specified, an auto-generated one is provided.
3476
*/
3577
String id() default "";
3678

3779
/**
38-
* The bean name of the {@link org.springframework.data.redis.listener.RedisMessageListenerContainer} to use.
39-
* If empty, the default 'redisMessageListenerContainer'
40-
* bean will be used
80+
* The bean name of the {@link org.springframework.data.redis.listener.RedisMessageListenerContainer} to subscribe
81+
* with. If empty, the default {@code RedisMessageListenerContainer} bean will be used.
4182
*/
42-
String container() default "redisMessageListenerContainer";
83+
String container() default "";
4384

4485
/**
45-
* The topics for this listener.
86+
* The destination name for this listener, resolved through a
87+
* {@link org.springframework.data.redis.listener.support.TopicResolver} strategy.
4688
*/
47-
String[] channels() default {};
89+
String topic() default "";
4890

49-
/**
50-
* The topic patterns for this listener.
51-
*/
52-
String[] patterns() default {};
5391
}

0 commit comments

Comments
 (0)