Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
今天我们用一个maven项目实战一个Spring Boot和Redis的详细整合与使用。
注意:本文中的com.fasterxml.jackson使用的依赖的版本号为2.9.10,不要使用到最新的版本2.12.4。Spring Boot和Redis的redis的版本最好使用最新版(当前最新为2.5.2),并且保持两者版本一致,避免不兼容的情况。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.2version>
<relativePath/>
parent>
<groupId>com.aliangroupId>
<artifactId>redisartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>redisname>
<description>spring boot演示redisdescription>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<project.package.directory>targetproject.package.directory>
<java.version>1.8java.version>
<jackson.version>2.9.10jackson.version>
<fastjson.version>1.2.68fastjson.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>${jackson.version}version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatypegroupId>
<artifactId>jackson-datatype-jsr310artifactId>
<version>${jackson.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>${fastjson.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
首先我们先看下RedisAutoConfiguration的源码,源码路径:spring-boot-autoconfigure-2.5.2.jar包中org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
package org.springframework.boot.autoconfigure.data.redis;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({
RedisOperations.class})
@EnableConfigurationProperties({
RedisProperties.class})
@Import({
LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {
"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
通过源码可以看出,SpringBoot自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。可是实际使用中会有几个问题:
我们实际中更常用到的是
我们顺便可以看看ConditionalOnMissingBean 源码
package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({
OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
Class<?>[] value() default {
};
String[] type() default {
};
Class<?>[] ignored() default {
};
String[] ignoredType() default {
};
Class<? extends Annotation>[] annotation() default {
};
String[] name() default {
};
SearchStrategy search() default SearchStrategy.ALL;
Class<?>[] parameterizedContainer() default {
};
}
从ConditionalOnMissingBean 的源码以及上面RedisAutoConfiguration 的源码,我们知道如果Spring容器中有bean name为redisTemplate对象了,这个自动配置的RedisTemplate就不会实例化。因此我们可以直接自己写个配置类,配置RedisTemplate。
废话不多说,直接上代码。这里再次提醒:com.fasterxml.jackson使用的依赖的版本号为2.9.10,具体可以看下我的maven依赖。
RedisConfig.java
package com.alian.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class RedisConfig {
/**
* redis配置
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 实例化redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// key采用String的序列化
redisTemplate.setKeySerializer(keySerializer());
// value采用jackson序列化
redisTemplate.setValueSerializer(valueSerializer());
// Hash key采用String的序列化
redisTemplate.setHashKeySerializer(keySerializer());
// Hash value采用jackson序列化
redisTemplate.setHashValueSerializer(valueSerializer());
//执行函数,初始化RedisTemplate
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* key类型采用String序列化
*
* @return
*/
private RedisSerializer<String> keySerializer() {
return new StringRedisSerializer();
}
/**
* value采用JSON序列化
*
* @return
*/
private RedisSerializer<Object> valueSerializer() {
//设置jackson序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//设置序列化对象
jackson2JsonRedisSerializer.setObjectMapper(getMapper());
return jackson2JsonRedisSerializer;
}
/**
* 使用com.fasterxml.jackson.databind.ObjectMapper
* 对数据进行处理包括java8里的时间
*
* @return
*/
private ObjectMapper getMapper() {
ObjectMapper mapper = new ObjectMapper();
//设置可见性
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//默认键入对象
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//设置Java 8 时间序列化
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
//禁用把时间转为时间戳
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.registerModule(timeModule);
return mapper;
}
}
如果redis是单节点,application.properties 配置如下:(为了演示方便,我这里Redis数据库索引使用的是1)
# Redis数据库索引(默认为0,设置为1是为了演示方便)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=192.168.0.193
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=200
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接超时时间(毫秒)
spring.redis.timeout=1000
如果是redis集群,application.properties 配置如下:(集群节点至少需要3组主从)
#集群地址配置,值为 host:port,用逗号分隔
spring.redis.cluster.nodes=192.168.0.111:6379,192.168.0.113:6379,192.168.0.101:6379,192.168.0.102:6379,192.168.0.103:6379,192.168.0.114:6379,192.168.0.104:6379
整合完毕后,不管是单节点的redis,还是redis集群,都可以进行如下的测试:
RedisService .java
package com.alian.redis.service;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* PostConstruct在构造函数之后,init()方法之前执行。
*/
@PostConstruct
public void redisTestWithPostConstruct() {
System.out.println("--------------------redis中String(字符串)测试开始--------------------");
stringRedis();
System.out.println("--------------------redis中String(字符串)测试结束--------------------");
System.out.println();
System.out.println("--------------------redis中list(列表)测试开始--------------------");
listRedis();
System.out.println("--------------------redis中list(列表)测试List结束--------------------");
System.out.println();
System.out.println("--------------------redis中Hash(散列)开始--------------------");
hashRedis();
System.out.println("--------------------redis中Hash(散列)结束--------------------");
}
private void stringRedis() {
// 定义一个json对象
JSONObject json = new JSONObject();
json.put("title", "SpringBoot整合redis(单节点)");
json.put("author", "Alian");
json.put("webUrl", "https://blog.csdn.net/Alian_1223");
json.put("publishTime", LocalDateTime.now());
//定义也给缓存的key
String cacheKey = "com.alian.csdn.redis.string";
// 缓存json字符串,同时设置过期时间为5分钟
redisTemplate.opsForValue().set(cacheKey, json.toJSONString(), 40, TimeUnit.SECONDS);
//从缓存中获取到的缓存的值
String jsonValue = (String) redisTemplate.opsForValue().get(cacheKey);
System.out.println("[String(字符串)],从缓存中获取到的缓存的值:" + jsonValue);
}
private void listRedis() {
//定义一个List对象
List<Object> list = new ArrayList<>();
list.add("apple");
list.add("orange");
list.add("pear");
//加入一个java8时间,检测是否序列化
list.add(LocalDateTime.now());
//定义也给缓存的key
String cacheKey = "com.alian.csdn.redis.list";
// 缓存list,同时设置过期时间为30秒(列表的左侧放入)
redisTemplate.opsForList().leftPushAll(cacheKey, list);
redisTemplate.expire(cacheKey, 3, TimeUnit.MINUTES);
//从缓存中获取到的缓存的值
List<Object> value = redisTemplate.opsForList().range(cacheKey, 0, -1);
System.out.println("[list(列表)],从缓存中获取到的缓存的值:" + value);
}
private void hashRedis() {
//定义一个map对象
Map<String, Object> map = new HashMap<>();
map.put("name", "梁南生");
map.put("sex", "男");
map.put("salary", "120000");
map.put("birth", LocalDate.of(1995, 8, 12));
//定义也给缓存的key
String cacheKey = "com.alian.csdn.redis.hash";
// 缓存map,同时设置过期时间为2小时
redisTemplate.opsForHash().putAll(cacheKey, map);
redisTemplate.expire(cacheKey, 2, TimeUnit.HOURS);
//从缓存中获取姓名和生日
List<Object> list = redisTemplate.opsForHash().multiGet(cacheKey, Arrays.asList("name", "birth"));
System.out.println("[Hash(散列)],从缓存中获取到的姓名和生日为:" + list);
}
}
这里我教大家一个新的方法进行简单的单元测试,那就是注解@PostConstruct,这个注解不是Spring的注解,而是java原生的javax.annotation.PostConstruct,使用上需要注意的如下:
缺点是服务启动时间稍慢一点,启动时加载的顺序
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
运行结果:
--------------------redis中String(字符串)测试开始--------------------
[String(字符串)],从缓存中获取到的缓存的值:{"publishTime":"2021-07-14T17:11:47.275","author":"Alian","webUrl":"https://blog.csdn.net/Alian_1223","title":"SpringBoot整合redis(单节点)"}
--------------------redis中String(字符串)测试结束--------------------
--------------------redis中list(列表)测试开始--------------------
[list(列表)],从缓存中获取到的缓存的值:[2021-07-14 17:11:47, pear, orange, apple]
--------------------redis中list(列表)测试List结束--------------------
--------------------redis中Hash(散列)开始--------------------
[Hash(散列)],从缓存中获取到的姓名和生日为:[梁南生, 1995-08-12]
--------------------redis中Hash(散列)结束--------------------
最后我们看下Java8的时间是否已经按照我们的格式(yyyy-MM-dd HH:mm:ss)序列化好,从客户端查看如下图所示:
关于Java8时间的序列化需要注意的是,序列化对象的值类型是Java8时间类型,而不是某个对象如json或字符串里包含一个java8时间类型的属性,至此,我们的redis里的key,value的序列化方式也实现了。
本文主要把Spring Boot和Redis进行详细整合,包括单节点redis和redis集群,也对部分原理进行简单介绍,这个整合在实际开发中是比较实用和灵活的,比如灵活的切换单节点redis和redis集群,使用上并没有半点不同。