我不希望在Spring Data Redis中使用@Bean创建RedisMessageListenerContainer来订阅
简而言之
当使用Spring Data Redis进行Subscribe时,可以通过@Bean定义RedisMessageListenerContainer如下所示。
虽然这种方式可以正常工作,但是它过于声明式且缺乏灵活性,有没有办法更加动态地实现呢?本文记录了根据模糊要求进行调查的结果。
请注意,这可能是大多数人都无用的垃圾文章。
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("aa.bb.cc.dd", 6379));
}
// Subscriber
@Bean
RedisMessageListenerContainer redisContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory());
container.addMessageListener(new Subscriber(), new ChannelTopic("channel1"));
return container;
}
}
除了RedisConfig.java之外的源代码。
除了RedisConfig.java之外的其他源代码。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class Publisher {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void publish(String channel, String message) {
redisTemplate.convertAndSend(channel, message);
}
}
package com.example.demo;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
public class Subscriber implements MessageListener {
public void onMessage(Message message, byte[] pattern) {
System.out.println(new String(pattern) + "/" + message.toString());
}
}
用这个运行下面的测试。
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ApplicationTests {
@Autowired
Publisher publisher;
@Test
void contextLoads() {
publisher.publish("channel1", "message1");
}
}
发挥力量
channel1/message1
经过确认,可以确认它是有效运行的。
想去掉@Bean
通过各种搜索(参考文献.1),我发现了一个好像很适合在CommandLineRunner中使用的方法,叫做GenericApplicationContext#registerBean。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
@Component
public class Runner implements CommandLineRunner {
@Autowired
GenericApplicationContext context;
@Autowired
LettuceConnectionFactory redisConnectionFactory;
@Override
public void run(String... args) throws Exception {
context.registerBean(RedisMessageListenerContainer.class, () -> {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener(new Subscriber(), new ChannelTopic("channel1"));
return container;
});
}
}
尝试从RedisConfig.java中删除@Bean注解。
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("13.231.239.64", 6379));
}
// Subscriber
// @Bean
// RedisMessageListenerContainer redisContainer() {
// RedisMessageListenerContainer container = new RedisMessageListenerContainer();
// container.setConnectionFactory(redisConnectionFactory());
// container.addMessageListener(new Subscriber(), new ChannelTopic("channel1"));
// return container;
// }
}
无产出
一些原因導致它無法正確運作。當執行context.getBean(RedisMessageListenerContainer.class)時,似乎已經註冊了該Bean。因此,我決定比較使用@Bean和registerBean兩種方式所創建的物件。結果發現RedisMessageListenerContainer的start字段存在差異。
@Beanした場合はstart=true
registerBeanした場合はstart=false
我来试一下开始
我感觉只需要调用 RedisMessageListenerContainer#start 方法就可以了,所以我决定试着调用一下。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
@Component
public class Runner implements CommandLineRunner {
@Autowired
GenericApplicationContext context;
@Autowired
LettuceConnectionFactory redisConnectionFactory;
@Override
public void run(String... args) throws Exception {
context.registerBean(RedisMessageListenerContainer.class, () -> {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener(new Subscriber(), new ChannelTopic("channel1"));
return container;
});
RedisMessageListenerContainer obj = context.getBean(RedisMessageListenerContainer.class);
obj.start();
}
}
我之前也尝试在return container之前加入了一些内容,但是这种情况下并没有正常工作。
就像上述所示,先注册一次再通过getBean方法取出,然后调用start方法就能够成功。
产生功效
channel1/message1
总结
使用GenericApplicationContext#registerBean可以达到一开始消除@Bean的目的,并且可以进行替代。但是,由于启动和不启动的行为差异,关于RedisMessageListenerContainer,建议按照Spring Data Redis文档中推荐的方法使用@Bean进行注册。
据参考文献.2所述,据说这是从Spring Framework 5.0开始引入的功能,但我不太清楚它具体有什么用处。可能是因为之前只是凭感觉使用过@Bean等功能,所以对于这方面的理解有所加深。