SpringBoot中使用注解读取redis缓存

一、介绍

我们使用redis的时候,一般都是以下这个步骤

  1. 查询指定的redis缓存

  2. 如果有直接返回,(异步执行查询,更新redis缓存)

  3. 如果没有则执行查询,(同时设置redis缓存)

此外,如果是增删改操作,将触发一次设置redis缓存的操作。

上面的一些步骤高度重复,我决定造个轮子,基于注解、切面和反射来完成此项功能。

二、相关代码

1)依赖

处于SpringBoot中,redisaop等相关依赖不要忘记

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2)代码

首先,我们先编写一个注解

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
package com.banmoon.test.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCache {

String value() default "hash";

RedisCacheKeyModel keyModel() default RedisCacheKeyModel.METHOD_HASH;

boolean write() default false;

/**
* 生成key的模式策略
*/
enum RedisCacheKeyModel {

/**
* 自定义的key
*/
CUSTOM(),

/**
* 方法名_hash值
*/
METHOD_HASH();

}

}

其次,我们编写切面了,针对上面注解的方法

简单的来说,就是获取注解标注的方法,通过注解上的参数,来确定key

有了key,先查询一遍redis,如果有值就直接返回;异步调用方法,并更新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
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.banmoon.test.aspect;

import com.banmoon.test.annotations.RedisCache;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

@Slf4j
@Aspect
@Component
public class RedisCacheAspect {

@Autowired
private RedisTemplate redisTemplate;

@Pointcut("@annotation(com.banmoon.test.annotations.RedisCache)")
public void pointcut() {
}

@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String key = "";
try {
String methodName = joinPoint.getSignature().getName();
Class<?> classTarget = joinPoint.getTarget().getClass();
Class<?>[] par = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
Method objMethod = classTarget.getMethod(methodName, par);

RedisCache cache = objMethod.getAnnotation(RedisCache.class);
if (RedisCache.RedisCacheKeyModel.CUSTOM == cache.keyModel()) {
key = cache.value();
} else {
String canonicalName = String.format("%s.%s", classTarget.getCanonicalName(), methodName);
key = String.format("%s_%s", methodName, canonicalName.hashCode());
}

if (cache.write()) {
Object res = joinPoint.proceed();
redisTemplate.opsForValue().set(key, res);
return res;
} else {
Object res = redisTemplate.opsForValue().get(key);
if (Objects.isNull(res)) {
res = joinPoint.proceed();
redisTemplate.opsForValue().set(key, res);
} else {
String finalKey = key;
CompletableFuture.runAsync(() -> {
try {
Object res1 = joinPoint.proceed();
redisTemplate.opsForValue().set(finalKey, res1);
} catch (Throwable throwable) {
}
});
}
return res;
}
} catch (Exception e) {
Object res = joinPoint.proceed();
redisTemplate.opsForValue().set(key, res);
return res;
}
}

}

3)测试使用

写一段测试方法,进行使用

TestController.java

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
package com.banmoon.test.controller;

import com.banmoon.test.dto.ResultData;
import com.banmoon.test.dto.UserDTO;
import com.banmoon.test.service.impl.TestServiceImpl;
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;

import java.util.List;

@RestController
@RequestMapping("/test")
public class TestController {

@Autowired
private TestServiceImpl testServiceImpl;

@GetMapping("/redisCache")
public ResultData redisCache() {
List<UserDTO> userList = testServiceImpl.getUserList();
return ResultData.success(userList);
}

}

TestServiceImpl.java

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
package com.banmoon.test.service.impl;

import com.banmoon.test.annotations.RedisCache;
import com.banmoon.test.dto.UserDTO;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class TestServiceImpl {

@RedisCache("userList")
public List<UserDTO> getUserList() {
List<UserDTO> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
UserDTO dto = new UserDTO();
dto.setName("半月无霜" + i);
dto.setSex("男");
dto.setAge(18+i);
dto.setStatus(i%2);
list.add(dto);
}
return list;
}

}

请求url,http://localhost:8080/test/redisCache,可以正常返回,同时查看`redis`有无缓存

请求结果 redis缓存
image-20220901153911398 image-20220901153911398

三、最后

注解、aop和反射配在一起可以做很多事!

许多轮子不就这样出来了么。

我是半月,祝你幸福!!!