背景:由于自研框架,好多公司都自己封装自己的Redis,所以需要单独讲redis封装起来,作为公举模块给业务系统用。自动装配原理就不介绍了,网上一搜一大堆,说的都不错。废话不多数,上代码
目录
项目结构:
1.springboot 版本号选择
2.项目根pom配置 lilock-framework
3.公共模块pom配置 lilock-commons
4.redis模块pom配置 lilock-redis-spring-boot-starter
4.1 自定义redis参数配置类
4.2 自定义redis工具类 RedisService
4.3 自定义redis配置类
4.4 springboot装配文件 spring.factories
5.新建lilock-server-user模块
5.1 application.yml配置redis参数
5.2 新建测试类
5.3 测试结果
lilock-framework lilock-commons lilock-common-spring-boot-starter lilock-redis-spring-boot-starter lilock-modules lilock-service-user
我自己选定的版本号是 2.3.12.RELEASE,可以根据自己公司对springboot的版本号情况自己选定
org.springframework.boot spring-boot-starter-parent 2.3.12.RELEASE
4.0.0
lilock-commons
lilock-modules
org.springframework.boot
spring-boot-starter-parent
2.3.12.RELEASE
lilock.cn
lilock-framework
1.0-SNAPSHOT
lilock-framework
pom
8
2.3.12.RELEASE
1.18.26
2.14.2
3.8.0
2.9.2
1.2.70
2.1.0.RELEASE
org.springframework.boot
spring-boot-starter-web
${spring.boot.version}
org.springframework.boot
spring-boot-starter-test
${spring.boot.version}
org.springframework.boot
spring-boot-autoconfigure
${spring.boot.version}
org.springframework.boot
spring-boot-starter-data-redis
${spring.boot.version}
org.springframework.boot
spring-boot-configuration-processor
${spring.boot.version}
io.springfox
springfox-swagger-ui
${swagger.version}
com.alibaba
fastjson
${fastjson.version}
io.springfox
springfox-swagger2
${swagger.version}
redis.clients
jedis
${jedis.version}
org.projectlombok
lombok
${lombok.version}
com.fasterxml.jackson.core
jackson-core
${jackson.version}
com.fasterxml.jackson.core
jackson-annotations
${jackson.version}
com.fasterxml.jackson.core
jackson-databind
${jackson.version}
org.springframework.boot
spring-boot-maven-plugin
lilock-framework
lilock.cn
1.0-SNAPSHOT
4.0.0
lilock-commons
pom
lilock-redis-spring-boot-starter
lilock-commons
lilock.cn
1.0-SNAPSHOT
4.0.0
lilock-common-spring-redis-starter
org.springframework.boot
spring-boot-autoconfigure
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-configuration-processor
redis.clients
jedis
com.alibaba
fastjson
org.projectlombok
lombok
com.fasterxml.jackson.core
jackson-core
com.fasterxml.jackson.core
jackson-annotations
com.fasterxml.jackson.core
jackson-databind
org.springframework.boot
spring-boot-maven-plugin
org.springframework.boot
spring-boot-configuration-processor
上述配置Redis自动装配模块的所有配置,开始进入主题
package lilock.cn.common.redis.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "lilock.redis")
public class RedisDataSourceProperties {
//链接地址
private String host;
//端口号
private Integer port;
//默认database
private Integer database;
//密码
private String password;
//超时时间
private Integer timeout;
//最大空闲数
private Integer maxIdle;
//连接池最大连接数
private Integer maxTotal;
//最大等待时间
private Integer maxWaitMillis;
//逐出链接最小空闲时间
private Integer minEvictableIdleTimeMillis;
//每次最大逐出数量
private Integer numTestsPerEvictionRun;
//逐出扫描时间的间隔
private Long timeBetweenEvictionRunsMillis;
///是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
private boolean testOnBorrow;
//空闲时检查有效性
private boolean testWhileIdle;
}
package lilock.cn.common.redis.template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.CollectionUtils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Redis Repository
* redis 基本操作 可扩展,基本够用了
*
*/
@Slf4j
public class RedisService {
/**
* 默认编码
*/
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
/**
* key序列化
*/
private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
/**
* value 序列化
*/
private static final JdkSerializationRedisSerializer OBJECT_SERIALIZER = new JdkSerializationRedisSerializer();
/**
* Spring Redis Template
*/
private RedisTemplate redisTemplate;
public RedisService(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
this.redisTemplate.setKeySerializer(STRING_SERIALIZER);
this.redisTemplate.setValueSerializer(OBJECT_SERIALIZER);
}
/**
* 获取链接工厂
*/
public RedisConnectionFactory getConnectionFactory() {
return this.redisTemplate.getConnectionFactory();
}
/**
* 获取 RedisTemplate对象
*/
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
/**
* 清空DB
*
* @param node redis 节点
*/
public void flushDB(RedisClusterNode node) {
this.redisTemplate.opsForCluster().flushDb(node);
}
/**
* 添加到带有 过期时间的 缓存
*
* @param key redis主键
* @param value 值
* @param time 过期时间(单位秒)
*/
public void setExpire(final byte[] key, final byte[] value, final long time) {
redisTemplate.execute((RedisCallback) connection -> {
connection.setEx(key, time, value);
log.debug("[redisTemplate redis]放入 缓存 url:{} ========缓存时间为{}秒", key, time);
return 1L;
});
}
/**
* 添加到带有 过期时间的 缓存
*
* @param key redis主键
* @param value 值
* @param time 过期时间(单位秒)
*/
public void setExpire(final String key, final Object value, final long time) {
redisTemplate.execute((RedisCallback) connection -> {
RedisSerializer serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = OBJECT_SERIALIZER.serialize(value);
boolean f = connection.setEx(keys, time, values);
log.info("[redisTemplate redis]放入 缓存 url:{} ========缓存时间为{}秒,{}", key, time, f);
return 1L;
});
}
/**
* 一次性添加数组到 过期时间的 缓存,不用多次连接,节省开销
*
* @param keys redis主键数组
* @param values 值数组
* @param time 过期时间(单位秒)
*/
public void setExpire(final String[] keys, final Object[] values, final long time) {
redisTemplate.execute((RedisCallback) connection -> {
RedisSerializer serializer = getRedisSerializer();
for (int i = 0; i < keys.length; i++) {
byte[] bKeys = serializer.serialize(keys[i]);
byte[] bValues = OBJECT_SERIALIZER.serialize(values[i]);
connection.setEx(bKeys, time, bValues);
}
return 1L;
});
}
/**
* 一次性添加数组到 过期时间的 缓存,不用多次连接,节省开销
*
* @param keys the keys
* @param values the values
*/
public void set(final String[] keys, final Object[] values) {
redisTemplate.execute((RedisCallback) connection -> {
RedisSerializer serializer = getRedisSerializer();
for (int i = 0; i < keys.length; i++) {
byte[] bKeys = serializer.serialize(keys[i]);
byte[] bValues = OBJECT_SERIALIZER.serialize(values[i]);
connection.set(bKeys, bValues);
}
return 1L;
});
}
/**
* 添加到缓存
*
* @param key the key
* @param value the value
*/
public void set(final String key, final Object value) {
redisTemplate.execute((RedisCallback) connection -> {
RedisSerializer serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = OBJECT_SERIALIZER.serialize(value);
connection.set(keys, values);
log.debug("[redisTemplate redis]放入 缓存 url:{}", key);
return 1L;
});
}
/**
* 查询在这个时间段内即将过期的key
*
* @param key the key
* @param time the time
* @return the list
*/
public List willExpire(final String key, final long time) {
final List keysList = new ArrayList<>();
redisTemplate.execute((RedisCallback>) connection -> {
Set keys = redisTemplate.keys(key + "*");
for (String key1 : keys) {
Long ttl = connection.ttl(key1.getBytes(DEFAULT_CHARSET));
if (0 <= ttl && ttl <= 2 * time) {
keysList.add(key1);
}
}
return keysList;
});
return keysList;
}
/**
* 查询在以keyPatten的所有 key
*
* @param keyPatten the key patten
* @return the set
*/
public Set keys(final String keyPatten) {
return redisTemplate.execute((RedisCallback>) connection -> redisTemplate.keys(keyPatten + "*"));
}
/**
* 利用scan 查询 keyPattern关键字的key集合
* @param keyPattern
* @return
*/
public Set scan(final String keyPattern) {
Set keys = redisTemplate.execute((RedisCallback>) connection -> {
Set keysTmp = new HashSet<>();
Cursor cursor = connection.scan(KeyScanOptions.scanOptions().match( keyPattern + "*").count(1000).build());
while (cursor.hasNext()) {
keysTmp.add(new String(cursor.next()));
}
return keysTmp;
});
return keys;
}
/**
* 根据key获取对象
*
* @param key the key
* @return the byte [ ]
*/
public byte[] get(final byte[] key) {
byte[] result = redisTemplate.execute((RedisCallback) connection -> connection.get(key));
log.debug("[redisTemplate redis]取出 缓存 url:{} ", key);
return result;
}
/**
* 根据key获取对象
*
* @param key the key
* @return the string
*/
public Object get(final String key) {
Object resultStr = redisTemplate.execute((RedisCallback
package lilock.cn.common.redis.config;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lilock.cn.common.redis.properties.RedisDataSourceProperties;
import lilock.cn.common.redis.template.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import redis.clients.jedis.JedisPoolConfig;
import java.lang.reflect.Method;
import java.time.Duration;
/**
* redis 配置流程
* 1.通过yaml 或者properties配置文件获取对应的redis的相关参数
* 2.利用redis相关参数配置连接池
* 2.利用连接池配置链接工厂
* 3.利用链接工厂设置Redistemplate,设置key 和 value的序列化方式
* 4.封装redis工具,利用redistemplate跟redis交互
*/
@EnableConfigurationProperties
@EnableCaching //开启SpringCache 管理
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {
@Autowired
private RedisDataSourceProperties redisDataSourceProperties;
/**
* 配置key的生成策略
* @return
*/
@Bean
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(":");
sb.append(method.getName());
sb.append(":");
for (Object param : params) {
sb.append(param.toString());
}
return sb.toString();
}
};
}
/**
* 配置jedis连接池
* @return
*/
@Bean("jedisPool")
@Primary
public JedisPoolConfig jedisPoolConfig(){
//创建链接池
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//获取最大空闲数
jedisPoolConfig.setMaxIdle(redisDataSourceProperties.getMaxIdle());
//连接池最大连接数量
jedisPoolConfig.setMaxTotal(redisDataSourceProperties.getMaxTotal());
//连接池最大等待时间
//jedisPoolConfig.setMaxWait(Duration.ofSeconds(redisDataSourceProperties.getMaxTotal()));
//逐出链接最小空闲时间
//jedisPoolConfig.setMinEvictableIdleTime(Duration.ofSeconds(redisDataSourceProperties.getMinEvictableIdleTimeMillis()));
//每次逐出最大数量
jedisPoolConfig.setNumTestsPerEvictionRun(redisDataSourceProperties.getNumTestsPerEvictionRun());
//逐出扫描时间间隔
//jedisPoolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(redisDataSourceProperties.getTimeBetweenEvictionRunsMillis()));
//是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
jedisPoolConfig.setTestOnBorrow(redisDataSourceProperties.isTestOnBorrow());
//空闲检查失效性
jedisPoolConfig.setTestWhileIdle(redisDataSourceProperties.isTestWhileIdle());
log.info("项目启动加载Redis连接池信息:{}", JSONObject.toJSONString(redisDataSourceProperties));
return jedisPoolConfig;
}
/**
* 初始化RedisTemplate的配置,配置序列化and工厂
* @param jedisPoolConfig
* @return
*/
@Bean("jedisConnectionFactory")
@Primary
public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig){
//配置redis链接信息
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisDataSourceProperties.getHost(),redisDataSourceProperties.getPort());
//设置密码
redisStandaloneConfiguration.setPassword(RedisPassword.of(redisDataSourceProperties.getPassword()));
//设置默认database
redisStandaloneConfiguration.setDatabase(redisDataSourceProperties.getDatabase());
JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder();
//设置超时时间
jedisClientConfigurationBuilder.connectTimeout(Duration.ofSeconds(redisDataSourceProperties.getTimeout()));
//设置链接池
jedisClientConfigurationBuilder.usePooling().poolConfig(jedisPoolConfig);
//创建工厂对象
JedisConnectionFactory factory = new JedisConnectionFactory(redisStandaloneConfiguration,jedisClientConfigurationBuilder.build());
return factory;
}
/**
* 配置redis序列化方式
* @param factory
* @return
*/
@Bean("redisTemplate")
public RedisTemplate redisTemplate(RedisConnectionFactory factory){
RedisTemplate redisTemplate = new RedisTemplate<>();
//设置链接工厂
redisTemplate.setConnectionFactory(factory);
RedisSerializer keySerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// key采用String的序列化方式
redisTemplate.setKeySerializer(keySerializer);
// hashkey采用String的序列化方式
redisTemplate.setHashKeySerializer(keySerializer);
// value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// value序列化方式采用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
@Primary //@Primary: 当出现相同名字的bean是,优先使用使用了 @Primary 注解的bean
public RedisService redisUtil(@Qualifier("redisTemplate") RedisTemplate redisTemplate){
RedisService redisUtil = new RedisService(redisTemplate);
return redisUtil;
}
/**
* 设置SpringCacheManager ,通过在类名启用@EnableCaching 来开启springcache
* 配置完此bean之后,可以通过@Cacheable/@CachePut/@CacheEvict 来进行操作缓存
* @Cacheable : 根据key从缓存中取值,存在则获取后直接返回,不执行业务方法;不存在则执行业务方法,讲业务方法返回的数据存入缓存
* @CachePut : 根据key从缓存中取值,无论数据是否存在,业务方法都会执行,并且业务方法返回的数据存入内存
* @CacheEvict : 执行业务方法之后,删除缓存中的数据
* @param factory
* @return
*/
@Bean("cacheManager")
@Primary
public CacheManager cacheManager(RedisConnectionFactory factory){
RedisCacheConfiguration redisAutoConfiguration = RedisCacheConfiguration
.defaultCacheConfig() //获取redisCache实例
.entryTtl(Duration.ofHours(1L)) //设置缓存过期时间
.computePrefixWith(cacheName -> "cache".concat(":").concat(cacheName).concat(":")) //给key值拼接前缀 cache: 类似于传入 key 最后存的时候 是 cache:key
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //设置key序列化方式
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) //设置value序列化方式
;
RedisCacheManager redisCacheManager = RedisCacheManager
.builder(factory) //获取redisCacheManager
.cacheDefaults(redisAutoConfiguration)
.build();
return redisCacheManager;
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ lilock.cn.common.redis.properties.RedisDataSourceProperties,\ lilock.cn.common.redis.config.RedisConfig
到目前位置,redis 自动装配已经完成,接下来就是测试了
引入 lilock-common-spring-redis-starter
lilock-modules
lilock.cn
1.0-SNAPSHOT
4.0.0
1.0-SNAPSHOT
lilock-service-user
jar
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
lilock.cn
lilock-common-spring-redis-starter
${project.version}
io.springfox
springfox-swagger2
io.springfox
springfox-swagger-ui
org.springframework.boot
spring-boot-maven-plugin
lilock: redis: host: 127.0.0.1 port: 6379 max-idle: 10 #设置最大空闲连接数,默认为8 max-wait-millis: -1 #获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 max-total: 20 #设置最大连接数,默认18个 min-evictable-idle-time-millis: 18000 #设置连接最小的逐出间隔时间,默认1800000毫秒 num-tests-per-eviction-run: 5 #每次逐出检查时,逐出连接的个数 默认为3 time-between-eviction-runs-millis: -1 #设置连接对象有效性扫描间隔,设置为-1,则不运行逐出线程 timeout: 10 #链接超时时间默认2秒 test-on-borrow: true # 在连接对象返回时,是否测试对象的有效性,默认false test-while-idle: true # 在连接池空闲时是否测试连接对象的有效性,默认false database: 0 password: server: port: 8988 spring: profiles: active: dev
package lilock.cn.user.controller;
import io.swagger.annotations.Api;
import lilock.cn.common.redis.template.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "/user")
@Api(value = "系统用户",tags = {"系统用户"})
@Slf4j
public class UserController {
@Autowired
private RedisService redisService;
@GetMapping("/test")
public String getUser(){
log.info("{}",redisService);
redisService.setExpire("zs","张三",1000);
String value = redisService.getStrValue("zs");
return value;
}
}
搞定!