上篇我们粗略的介绍了下Spring的缓存抽象。知道了如果不引入任何缓存的依赖,默认使用的是ConcurrentHashMap
进行缓存。接下来我们介绍下SpringBoot与Redis的整合。
一、快速上手
注意:因为篇(其)幅(是)原(是)因(懒),Redis环境搭建就不说了,我这里假设大家都搭建好了Redis环境。
- 在创建Spring项目的时候,勾选redis。SpringBoot会自动帮我们引入Redis的起步依赖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<dependencies>
<!-- 引入Redis的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> - 配置Redis。我这边只配置Redis的地址、端口。其他的暂时不配置。
1
2
3
4spring:
redis:
host: 192.168.25.151 # Redis地址
port: 6379 #redis 端口 - 既然依赖有了。配置文件也有了。那就开始coding吧。这里我们使用两种方法,即使用
RedisTemplate
和Spring缓存抽象
- 公共部分(包含Controller、dao)
- Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"/user") (
public class UserController {
private UserService userService;
//这个请求是测试Spring缓存抽象
"/getUser/{id}") (
public User getUser(@PathVariable("id") Integer id){
return userService.getUser(id);
}
//这个请求是测试RedisTemplate
"/getUser1/{id}") (
public User getUser1(@PathVariable("id") Integer id){
return userService.getUser1(id);
}
"/updateUser") (
public User updateUser(User user){
return userService.updateUser(user);
}
"/deleteUser/{id}") (
public User deleteUser(@PathVariable("id") Integer id){
return userService.deleteUser(id);
}
}- dao(为了方便,我就不查数据库了。自己构造个)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class UserDao {
private static Map<Integer,User> map = new HashMap<>();
static{
map.put(1,new User(1,"张三",18));
map.put(2,new User(2,"李四",19));
map.put(3,new User(3,"王五",17));
map.put(4,new User(4,"赵六",14));
map.put(5,new User(5,"王小二",20));
map.put(6,new User(6,"李小四",29));
map.put(7,new User(7,"赵明",34));
map.put(8,new User(8,"王柳",16));
map.put(9,new User(9,"李斯",17));
map.put(10,new User(10,"孙福",10));
}
public User getUser(int id){
System.out.println("use db select, id is " +id);
return map.get(id);
}
public User updateUser(User user) {
System.out.println("update User "+ user);
map.put(user.getId(),user);
return map.get(user.getId());
}
public User deleteUser(Integer id) {
System.out.println("delete user id is "+ id);
return map.remove(id);
}
}
- 缓存部分(包含Service)
- 使用RedisTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private RedisTemplate<Object,Object> template; //注意。使用AutoWired注入时,类型要与容器中的Bean类型一致。
public User getUser1(Integer id){
System.out.println("使用RedisTemplate");
//使用opsForValue方法操作Redis的strings类型,即key,value结构的数据
//从redis取指定的key值。如果有值就返回。没值就执行方法
if(template.opsForValue().get(id+"") !=null){
return (User)template.opsForValue().get(id+"");
}
User user = userDao.getUser(id);
if(user!=null){
//如果方法返回的值不为空,就放到Redis中
template.opsForValue().set(id+"",user);
}
return user;
}- 使用Spring缓存抽象
1
2
3
4
5
6//注意:如果想要启用缓存抽象的注解的话,需要在启动类上加上@EnableCaching注解。
//Spring已经帮我们统一管理。所以我们只需要之前的注解即可。
"UserCache",key="#id") (value =
public User getUser(Integer id){
return userDao.getUser(id);
}
- 公共部分(包含Controller、dao)
- 测试
- 结果我们就不说了。我们来看看在Redis中数据是怎么存的吧。
- 看图我们可以得出几个结论:
- 可见使用RedisTemplate和Spring缓存抽象,不是存储在一起的。这是因为Spring缓存抽象,会帮我们加一个前缀。这里在下面的源码分析中会说到。
- 看value可以发现,我们根本看不懂存的是啥。这是因为默认使用的是java序列化机制。当然也可以定义我们自己的机制。比如json。这个在后面会说到。
- 看图我们可以得出几个结论:
- 结果我们就不说了。我们来看看在Redis中数据是怎么存的吧。
二、原理初探
-
先说Spring缓存抽象这块吧。前两篇已经介绍得差不多了。
-
在SpringBoot启动的时候,会自动执行
CacheConfigurationImportSelector
的selectImports()
方法,获取CacheConfiguration类。这里我们因为引入了Redis的依赖。那么,这里我们使用的CacheConfiguration就是RedisCacheConfiguration
。那么我们就进去看看。 -
RedisCacheConfiguration
类分析1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62class RedisCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
private final org.springframework.data.redis.cache.RedisCacheConfiguration redisCacheConfiguration;
//注入配置文件、定制器、RedisCacheConfiguration
RedisCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
}
//创建CacheManger.注入ConnectionFactory
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
ResourceLoader resourceLoader) {
RedisCacheManagerBuilder builder = RedisCacheManager
.builder(redisConnectionFactory) //传入ConnectionFactory
//设置默认的RedisCacheConfiguration。determineConfiguration()方法会在下面分析
.cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
//获取配置的缓存名。
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
//执行定制器,并返回执行完后的RedisCacheManager对象
return this.customizerInvoker.customize(builder.build());
}
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
ClassLoader classLoader) {
if (this.redisCacheConfiguration != null) {
return this.redisCacheConfiguration;
}
//获取redis的配置信息
Redis redisProperties = this.cacheProperties.getRedis();
//先获取默认的CacheConfiguration
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();
//设置value的序列化机制。这里使用的是JDK的序列化机制。我们可以改成Json的
config = config.serializeValuesWith(SerializationPair
.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
//获取配置,如果不为空就设置
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
//这里就是设置key的前缀。因为我们没有配置。所以在redis存的就是没有前缀的。
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
//是否启用Redis Key的前缀redisProperties的UseKeyPrefix属性默认值为true
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
} -
至此,我们已经获取到了CacheManager。接下来就和之前一样了。在这里我们分析下
RedisCache
.分析下它是怎么存取的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//和之前的机制一样,先调用lookup方法看是否有值,如果有就返回,如果没有就返回null。随后会执行方法。
protected Object lookup(Object key) {
//通过缓存名和转化过的key去redis中取值
byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key));
//如果没值就返回null
if (value == null) {
return null;
}
//有值就返回解析后的值
return deserializeCacheValue(value);
}
//分析下createAndConvertCacheKey。看看是key的是怎么生成的。
private byte[] createAndConvertCacheKey(Object key) {
//调用createCacheKey创建key。再通过Redis序列化Key的机制序列化
return serializeCacheKey(createCacheKey(key));
}
//设置创建key的代码
protected String createCacheKey(Object key) {
//把key转成String类型。如果可以转就转成String。不能转就调用toString方法
String convertedKey = convertKey(key);
//判断是否开启前缀。默认是开启
if (!cacheConfig.usePrefix()) {
return convertedKey;
}
//返回组装了的前缀的key
return prefixCacheKey(convertedKey);
}
//组装key
private String prefixCacheKey(String key) {
// allow contextual cache names by computing the key prefix on every call.
//把缓存名传进去。里面的操作为,如果定义了前缀,就返回前缀。如果没有定义前缀就默认使用"缓存名::"作为前缀
return cacheConfig.getKeyPrefixFor(name) + key;
} -
现在让我们总结下吧。
RedisCacheConfiguration
会帮我们使用org.springframework.data.redis.cache.RedisCacheConfiguration
(注:该类和我们分析的类不是同一个)创建一个RedisCacheManager
.- 在创建
org.springframework.data.redis.cache.RedisCacheConfiguration
时。会设置value的序列化方式,默认为使用JDK的序列化机制。会帮我们设置前缀和缓存名。 - key的生成策略是先判断是否定义了前缀。如果定义了前缀就返回前置。否则返回
缓存名::
。再拼上我们传进来的key。得到最终存到Redis中的key
-
-
接下来,我们就看看
RedisTemplate
- 除了CacheAutoConfiguration之外,Redis也有一个自动配置类。下面我们看看他在自动配置类中做了什么。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.class) (RedisOperations
@EnableConfigurationProperties(RedisProperties.class)
//导入Lettuce和Jedis的配置类。两种只有一种生效。在配置类中建立RedisConnectionFactory。默认为Lettuce
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
//往容器中加入RedisTemplate。操作<object,object>
"redisTemplate") (name =
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
//创建StringRedisTemplate。专门用来操作<string,string>
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}- 这样我们就可以直接在service中使用自动注入来使用RedisTemplate。但是需要注意的是。如果使用的
@Autowired
.则声明的属性类型一定要与容器中的类型一致。例如,在容器中声明的是RedisTemplate<Object,Object>.则在service中也要是对应的RedisTemplate<Object,Object>。否则会报错。这是因为@Autowired
是按类型进行注入。可以使用@Resource
进行注入。就不会有这种问题。
三、如何定制?
- Spring缓存抽象中RedisManager的定制
- 在我们的配置类中,重新定义
RedisCacheManager
.这里以修改默认的value的序列化机制为json为例。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
public class RedisConfig {
public RedisCacheManager redisCacheManager(LettuceConnectionFactory factory){
//获取默认的RedisCacheConfiguration
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
//设置value序列化为jackson
configuration = configuration.serializeValuesWith(SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class)));
//创建RedisCacheManager对象
return RedisCacheManager.builder(factory).cacheDefaults(configuration).build();
}
}
- 在我们的配置类中,重新定义
- RedisTemplate的定制。
- 只需要重新定义下
RedisTemplate
即可。代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RedisConfig{
public RedisTemplate<String,User> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,User> template = new RedisTemplate<>();
//设置Redis连接工厂
template.setConnectionFactory(redisConnectionFactory);
//设置Redis Key的序列化机制
template.setKeySerializer(new StringRedisSerializer());
//设置Redis value的序列化机制为Json
template.setValueSerializer(new Jackson2JsonRedisSerializer(User.class));
return template;
}
}
- 只需要重新定义下
四、总结
- 整合步骤:
- 引入起步依赖
- 编写配置文件,配置redis的host和端口
- 使用
- 源码总结
- 使用CacheManager
- SpringBoot在启动时,会执行
RedisConfiguration
。为我们创建RedisManager
。默认value序列化使用的是JDK的序列化。 - 接下来就可以通过抽象注解使用了。使用方法还是和前面使用
ConcurrentHashMap
一样。默认会为我们加上缓存名::
的前缀
- SpringBoot在启动时,会执行
- 使用RedisTemplate
- 在SpringBoot启动时。会在
RedisAutoConfiguration
中,为我们创建RedisTemplate
和StringRedisTemplate
. - 我们可以在service或其他需要缓存的地方,使用
@Autowired
把RedisTemplate注入进来。
- 在SpringBoot启动时。会在
- 使用CacheManager
以上就是所有内容了。缓存篇也到此结束,下面带来的是SpringBoot与消息中间件的整合。咱们下篇再见~感谢观看!
评论