我不希望在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等功能,所以对于这方面的理解有所加深。

参考:对于以下内容,请以中文原生地进行改写,只需一种改写方案:

:thumbsup:
广告
将在 10 秒后关闭
bannerAds