SpringBoot整合Jedis实现监听键过期通知和消息订阅
开局的一些废话
在学习Redis命令手册的时候,订阅键过期的命令使用的时SUBSCRIBE命令。这个命令可以收到键的过期通知,但是无法接收到匹配模式的订阅消息,如果我要是既想要接收到键的过期通知,也想要接收到推送的消息,应该怎么办呢?用PSUBSCRIBE命令就可以做到。另外一个问题就是,在网上的一些文章中,大部分使用的都是SpringBoot自带的Redis工具,在Application.properties文件中配置Redis客户端的一些连接信息即可,但是如果我要是想用自己的客户端,应该怎么办?这篇文章包含以下内容:
- SpringBoot项目整合Jedis客户端
- @Autowired在非Controller中注入位空的问题
- Jedis订阅键过期通知和消息推送
创建项目
创建一个普通的springBoot项目即可,这部分就省略了。
添加相关的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<!--<artifactId>spring-boot-starter-redis</artifactId>-->
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
创建Redis配置文件
在SpringBoot的目录中,创建一个redis.properties文件,这个文件中主要是配以一些Redis的链接相关的信息,内容如下:
#redis配置开始
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=zxc123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=1024
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=10000
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=200
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=10
# 连接超时时间(毫秒)
spring.redis.timeout=10000
spring.redis.block-when-exhausted=true
创建Redis配置类,读取配置信息
既然已经创建了redis的配置文件,那么我们肯定需要创建一个redis的配置类,来使用这些配置信息,这里的配置类我就叫做RedisConfig.java,以下是这个类的代码
@Configuration
@PropertySource("classpath:redis.properties")
@Slf4j
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.jedis.pool.max-wait}")
private long maxWaitMillis;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.block-when-exhausted}")
private boolean blockWhenExhausted;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public JedisPool reidsPoolFactory(){
log.info("JedisPool注入成功!!");
log.info("redis地址:"+host+":"+port);
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig() ;
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
// 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
jedisPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
// 是否启用pool的jmx管理功能, 默认true
jedisPoolConfig.setJmxEnabled(true);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);
jedisPool.getResource();
return jedisPool;
}
解决@Autowired注入为空
我们既然已经整合了Jedis,那么我们肯定是希望可以使用Jedis来订阅消息的,但是SpringBoot如果我们在一个普通的配置类里面使用@AutoWired注解注入对象的时候,注入的是一个null,那这个问题应该怎么解决呢?这里就直接把代码复制过来了,这里主要是通过@PostConstruct注解来解决的这个问题,在CSDN上同样有很多关于这个注解的说明,可以自行去查看,这里就不在过多的说明。如果我们想要获取一个Jedis的话,只需要调用这个getjedis的静态方法就可以了
@Component
public class JedisPoolAutowried {
@Autowired
private JedisPool jedisPool;
private static JedisPoolAutowried redisKeyExpiredSubscriberRunnable;
//这个时为了解决Autowired在非controller注入位空的问题
@PostConstruct
public void init(){
redisKeyExpiredSubscriberRunnable = this;
redisKeyExpiredSubscriberRunnable.jedisPool = this.jedisPool;
}
public static Jedis getjedis(){
return redisKeyExpiredSubscriberRunnable.jedisPool.getResource();
}
}
监听消息通知
编写消息处理类
这个消息处理类需要继承JedisPubSub 这个类,在并且需要实现onPMessage这个方法,因为我们通过PSUBSCRIBE命令订阅的消息,收到的消息类型是PMESSAGE,会触发这个方法。这个类里面还有其他的一些方法,可以自行去查看。
public class RedisPsubscribeListener extends JedisPubSub {
@Override
public void onPMessage(String pattern, String channel, String message) {
System.out.println("Redis订阅监听超时通知开始pattern{"+pattern+" } channel{ "+channel+"} message{"+message+"}");
if (StringUtils.isBlank(message)){
System.out.println("Message消息为空");
return;
}
System.out.println("Redis订阅监听超时通知完成 pattern{"+pattern+" } channel{ "+channel+"} message{"+message+"}");
}
}
订阅消息
我们编写完消息处理类以后,需要让Jedis客户端来订阅这一些消息,代码如下:
这个类是一个线程,它实现了Runnable接口,需要注意的是,clientPsubscribe.psubscribe()是一个阻塞方法,它里面是通过do while循环来检测时间的,所以那个clientPsubscribe.close();方法一直后不会被执行,这里需要注意一下。并且psubscribe命令是支持匹配模式的,在下面的代码中,一共订阅了三种消息,第一个就是匹配模式,设置的是"news.* ",这代表以news.开头的消息,都可以检测到。例如:news.music,第二个是键过期通知,第三个就是一个普通的消息。
需要注意的一点是,如果想要收到键过期的通知,这里需要把Redis配置文件中的
notify-keyspace-events参数的值设置为Ex,或者是通过config set notify-keyspace-events Ex命令来设置。
public class RedisKeyExpiredSubscriberRunnable implements Runnable{
@Override
public void run() {
//这里使用的就是上面的Getjedis方法,可以从连接池中获取一个客户端,如果上面的JedisPoolAutowried 类中
//不使用@PostConstruct注解,那么在这里只会获取到一个null值
Jedis clientPsubscribe =JedisPoolAutowried.getjedis();
RedisPsubscribeListener redisPsubscribeListener = new RedisPsubscribeListener();
System.out.println("开始订阅过期事件");
clientPsubscribe.psubscribe(redisPsubscribeListener,"news.*","__keyevent@0__:expired","redismessage");
clientPsubscribe.close();
}
}
Springboot加载
在我们编写完了消息订阅和消息的处理的相关的方法后,剩下的就是让SpringBoot来加载我这些配置,我们需要再创建一个类,这个类是给SpringBoot来使用的。这个类实现了CommandLineRunner 接口,在SprigBoot启动的过程中,会自动的加载这个方法,通常可以使用这个接口用来在SpringBoot启动的时候加载一些数据。如果有多个需要加载,可以通过@Order(value = 1)//启动顺序来设置加载顺序。
@Component
@Order(value = 1)//启动顺序
public class RedisPublisherRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
//用于启动后执行,一般是初始化的时候使用
new Thread(new RedisKeyExpiredSubscriberRunnable()).start();
}
}
启动SpringBoot,推送消息
启动SpringBoot
启动SpringBoot后,控制台会输出一条信息,这条信息是通过println语句来输出的,上面的代码中应该可以看到这一行代码
推送消息
我们单独启动一个redis客户端,来输入一些消息,看看是否能够监听到
-
输入setex命令,设置一个具有过期时间的值
控制台输出的内容:
-
输入PUBLISH news.nusic,通过匹配模式来推送消息:
控制台输出的消息
-
推送一条普通的消息到redismessage频道下
控制台中显示的消息:
结束
到此,我们已经实现了一个简单的SpringBoot整合Jedis实现监听键过期通知和消息订阅的demo,这是本人第一个写文章,里面可能有一些描述不准确的地方,欢迎指正。另外,关于notify-keyspace-events参数更详细的配置,可以去redis命令手册中查看