文章目录
- 前言
- 正文
- 1、Lock4j的代码仓库
- 2、pine-manage-common-redis的项目结构
- 3、pine-manage-common-redis 的完整代码
- 3.1 maven依赖:pom.xml
- 3.2 redis连接参数:application.yaml
- 3.3 RedisCache.java
- 3.4 CacheConfig.java
- 3.5 RedissonClientUtil.java
- 3.6 SpringBeanUtil.java
- 3.7 RedisLock.java
- 3.8 RedisLockJsonKeyFunction.java
- 4、pine-manage-common-redis的使用
前言
在平时使用SpringBoot框架时,有些业务场景会需要使用到Redis。但是,常见的Redis使用方式有好几种:
-
spring-boot-starter-data-redis
:这是Spring Boot官方提供的Redis starter,它基于Spring Data Redis,提供了对Redis的集成支持。它默认使用Lettuce作为Redis客户端,但也支持Jedis客户端。通过这个starter,可以快速地在Spring Boot中整合、使用Redis,进行数据缓存、会话管理等操作。 -
spring-boot-starter-integration
:这个starter提供了Spring Integration的支持,可以与Redis结合使用,实现分布式锁等功能。 -
spring-integration-redis
:这是Spring Integration项目的一部分,提供了与Redis的集成支持,可以用于实现分布式锁等高级功能。 -
redisson-spring-boot-starter
:Redisson是一个在Java环境下的Redis客户端,它提供了一系列的分布式数据结构和服务,如分布式锁、原子变量、集合等。这个starter简化了Redisson在Spring Boot中的配置和使用。 -
spring-boot-starter-data-redis-reactive:基于Webflux的响应式redis组件。
一般而言,我们会选择其中的一个。但是最近笔者发现了小黑鸟https://baomidou.com/resources/eco-system/ 的一个开源组件,就试着用了下。最后得到的结果是:真香!!!
这个Lock4j 本身是主要用于做分布式锁的。但是我们加入缓存组件,就能很容易得到一个具备缓存功能,同时支持分布式锁的一个小工具。
正文
1、Lock4j的代码仓库
https://gitee.com/baomidou/lock4j
Lock4j 本身支持集成多种Redis的连接器,我这次主要使用Redisson的集成。
我的集成项目实例仓库地址:https://gitee.com/fengsoshuai/pine-manage-system.git
进入pine-manage-system项目后,pine-manage-common-redis
模块是本文的重点。
注意:我的项目使用了java17版本,SpringBoot版本是3.3.4,如果想直接复用,需要自己调整。
2、pine-manage-common-redis的项目结构
com.pine.common.redis.cache
:缓存包,实现了redis做缓存时的基本方法。com.pine.common.redis.config
:配置包,配置了缓存参数,redisson的相关配置,还有Spring获取Bean的工具类。com.pine.common.redis.lock
:redis做分布式锁的包装,实现了常用的工具方法。并自定义了redis key的拼接方法。
3、pine-manage-common-redis 的完整代码
3.1 maven依赖:pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.pine</groupId><artifactId>pine-manage-common</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>pine-manage-common-redis</artifactId><packaging>jar</packaging><name>pine-manage-common-redis</name><url>http://maven.apache.org</url><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><lock4j.version>2.2.7</lock4j.version><redisson.boot.version>3.25.2</redisson.boot.version></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- 使用lock4j实现分布式锁 https://gitee.com/baomidou/lock4j/--><dependency><groupId>com.baomidou</groupId><artifactId>lock4j-core</artifactId><version>${lock4j.version}</version></dependency><!-- 引入redisson--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>${redisson.boot.version}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>lock4j-redisson-spring-boot-starter</artifactId><version>${lock4j.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jsr310</artifactId></dependency><!-- Spring Boot Starter Cache for integration --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency></dependencies>
</project>
3.2 redis连接参数:application.yaml
# redis配置 -使用时需要配置自己的redis
spring:data:redis:host: localhostport: 6379
3.3 RedisCache.java
package com.pine.common.redis.cache;import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;import java.util.Objects;/*** redis缓存** @author pine manage* @since 2024-08-13*/
@Slf4j
public class RedisCache {@Resource(name = "redissonCacheManager")private CacheManager redissonCacheManager;/*** 获取缓存** @param cacheName 缓存名称* @param key 缓存key* @return 缓存数据*/public Object get(String cacheName, String key) {log.info("RedisCache获取缓存,cacheName={}, key={}", cacheName, key);Cache cache = redissonCacheManager.getCache(cacheName);if (Objects.isNull(cache)) {return null;}// 从缓存中获取数据return cache.get(key);}/*** 设置缓存** @param cacheName 缓存名称* @param key 缓存key* @param value 缓存数据*/public void set(String cacheName, String key, Object value) {log.info("RedisCache设置缓存,cacheName={}, key={}, value={}", cacheName, key, value);Cache cache = redissonCacheManager.getCache(cacheName);if (Objects.isNull(cache)) {return;}// 将数据放入缓存cache.put(key, value);}/*** 移除缓存** @param cacheName 缓存名称* @param key 缓存key*/public void remove(String cacheName, String key) {log.info("RedisCache移除缓存,cacheName={}, key={}", cacheName, key);Cache cache = redissonCacheManager.getCache(cacheName);if (Objects.isNull(cache)) {return;}// 从缓存中移除数据cache.evict(key);}/*** 移除所有缓存** @param cacheName 缓存名称*/public void removeAll(String cacheName) {log.info("RedisCache移除所有缓存,cacheName={}", cacheName);Cache cache = redissonCacheManager.getCache(cacheName);if (Objects.isNull(cache)) {return;}// 从缓存中移除数据cache.clear();}
}
3.4 CacheConfig.java
package com.pine.common.redis.config;import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.pine.common.redis.cache.RedisCache;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.spring.cache.RedissonSpringCacheManager;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 缓存配置** @author pine manage* @since 2024-08-13*/
@Configuration
public class CacheConfig {@Bean(name = "redissonCacheManager")@ConditionalOnMissingBeanpublic CacheManager redissonCacheManager(@Autowired RedissonClient redissonClient) {// 可以自定义缓存配置return new RedissonSpringCacheManager(redissonClient);}@Bean@ConditionalOnMissingBeanpublic RedisCache redisCache() {return new RedisCache();}@Beanpublic RedissonAutoConfigurationCustomizer redissonAutoConfigurationCustomizer() {// 增加支持序列化java8的时间ObjectMapper objectMapper = new ObjectMapper();objectMapper.registerModule(new JavaTimeModule());JsonJacksonCodec jsonJacksonCodec = new JsonJacksonCodec(objectMapper);return config -> config.setCodec(jsonJacksonCodec);}
}
3.5 RedissonClientUtil.java
package com.pine.common.redis.config;import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;import java.time.Duration;/*** redissonClient 工具类** @author pine manage* @since 2024-08-22*/
public class RedissonClientUtil {private static RedissonClient redissonClient;public static RedissonClient getRedissonClient() {init();return redissonClient;}/*** 设置缓存** @param key key* @param value value* @param timeToLiveSeconds 失效时间(单位:秒)*/public static void setCacheWithExpiration(String key, Object value, long timeToLiveSeconds) {init();RBucket<Object> bucket = redissonClient.getBucket(key);// 设置缓存值和过期时间bucket.set(value, Duration.ofSeconds(timeToLiveSeconds));}/*** 设置缓存** @param key key* @param value value*/public static void setCache(String key, Object value) {init();RBucket<Object> bucket = redissonClient.getBucket(key);// 设置缓存值bucket.set(value);}/*** 获取缓存** @param key key* @return value*/public static Object getCache(String key) {init();RBucket<Object> bucket = redissonClient.getBucket(key);return bucket.get();}/*** 移除缓存** @param key key*/public static void removeCache(String key) {init();redissonClient.getBucket(key).delete();}private static void init() {if (redissonClient == null) {redissonClient = SpringBeanUtil.getByClass(RedissonClient.class);}}
}
3.6 SpringBeanUtil.java
package com.pine.common.redis.config;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;/*** springbean工具类** @author pine manage* @since 2024-08-09*/
@Component
public class SpringBeanUtil implements BeanFactoryAware {private static BeanFactory beanFactory;@Overridepublic void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {SpringBeanUtil.beanFactory = beanFactory;}public static <T> T getByClass(Class<T> clazz) {return beanFactory.getBean(clazz);}
}
3.7 RedisLock.java
package com.pine.common.redis.lock;import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.pine.common.redis.config.SpringBeanUtil;import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Function;/*** redis锁** @author pine manage* @since 2024-08-09*/
public class RedisLock {private static LockTemplate lockTemplate;/*** 获取锁超时时间*/private static final Long LIMIT_STREAM_ACQUIRE_TIMEOUT = 0L;private static final Long ACQUIRE_TIMEOUT = 3000L;/*** 锁过期时间*/private static final Long EXPIRE = 5000L;/*** redis分布式锁(redisson自动续锁)** @param bizRunnable 业务逻辑* @param key 锁key*/public static void lock(Runnable bizRunnable, String key, Long acquireTimeout) {initLockTemplate();// 获取锁超时时间,默认3秒acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(ACQUIRE_TIMEOUT);// expire锁过期时间为-1:不过期,redisson 自动续锁LockInfo lockInfo = lockTemplate.lock(key, -1L, acquireTimeout);if (lockInfo == null) {throw new RuntimeException("获取锁失败");}try {bizRunnable.run();lockTemplate.releaseLock(lockInfo);} catch (Exception e) {lockTemplate.releaseLock(lockInfo);throw e;}}/*** redis分布式锁(redisson自动续锁)** @param bizCallable 业务逻辑* @param key 锁key*/public static <Result> Result lock(Callable<Result> bizCallable, String key, Long acquireTimeout) throws Exception {initLockTemplate();// 获取锁超时时间,默认3秒acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(ACQUIRE_TIMEOUT);// expire锁过期时间为-1:不过期,redisson 自动续锁LockInfo lockInfo = lockTemplate.lock(key, -1L, acquireTimeout);if (lockInfo == null) {throw new RuntimeException("获取锁失败");}try {Result result = bizCallable.call();lockTemplate.releaseLock(lockInfo);return result;} catch (Exception e) {lockTemplate.releaseLock(lockInfo);throw e;}}/*** 加锁限流<br>* <b>此处注意:上锁后不手动解锁,等待过期时间自动解锁</b>** @param param 锁参数*/public static void lockAndLimitStream(Object param) {lockAndLimitStream(param, null, null, new RedisLockJsonKeyFunction());}/*** 加锁限流<br>* <b>此处注意:上锁后不手动解锁,等待过期时间自动解锁</b>** @param expire 锁过期时间* @param acquireTimeout 获取锁超时时间*/public static void lockAndLimitStream(Object param, Long expire, Long acquireTimeout) {lockAndLimitStream(param, expire, acquireTimeout, new RedisLockJsonKeyFunction());}/*** 加锁限流<br>* <b>此处注意:上锁后不手动解锁,等待过期时间自动解锁</b>** @param expire 锁过期时间* @param acquireTimeout 获取锁超时时间* @param keyFunction 计算锁key的函数*/public static void lockAndLimitStream(Object param, Long expire, Long acquireTimeout, Function<Object, String> keyFunction) {initLockTemplate();// 锁过期时间默认5秒expire = Optional.ofNullable(expire).orElse(EXPIRE);// 获取锁超时时间默认0秒acquireTimeout = Optional.ofNullable(acquireTimeout).orElse(LIMIT_STREAM_ACQUIRE_TIMEOUT);// 计算锁keyString key = keyFunction.apply(param);// 尝试获取锁LockInfo lockInfo = lockTemplate.lock(key, expire, acquireTimeout);// 获取锁失败if (lockInfo == null) {throw new RuntimeException("调用过于频繁,业务处理中,请稍后再试");}}/*** 初始化lockTemplate*/private static void initLockTemplate() {if (lockTemplate == null) {lockTemplate = SpringBeanUtil.getByClass(LockTemplate.class);}Objects.requireNonNull(lockTemplate, "lockTemplate is null");}
}
3.8 RedisLockJsonKeyFunction.java
package com.pine.common.redis.lock;import com.fasterxml.jackson.databind.ObjectMapper;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;/*** redis锁key生成函数** @author pine manage* @since 2024-08-09*/
public class RedisLockJsonKeyFunction implements Function<Object, String> {private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();private static final Lock LOCK = new ReentrantLock();/*** Applies this function to the given argument.** @param object the function argument* @return the function result*/@Overridepublic String apply(Object object) {return serializeToJson(object);}/*** 序列化对象为json** @param object 对象* @return json*/private static String serializeToJson(Object object) {try {LOCK.lock();return OBJECT_MAPPER.writeValueAsString(object);} catch (Exception e) {throw new RuntimeException(e);} finally {LOCK.unlock();}}
}
4、pine-manage-common-redis的使用
在要使用的模块中,引入如下依赖:
<dependency><groupId>com.pine</groupId><artifactId>pine-manage-common-redis</artifactId><version>${pine-manage-common-redis.version}</version></dependency>
随后直接使用RedisLock的静态方法即可,比如:
// 限流
RedisLock.lockAndLimitStream(triggerContext, expire, acquireTimeout);
而如果要使用缓存功能,则直接注入 RedisCache
,然后使用即可,比如:
@Resource
protected RedisCache redisCache;// 设置缓存
redisCache.set(CacheNameConstant.SYS_CONFIG_CACHE_NAME, sysConfig.getConfigKey(), newSysConfigBo);