redis缓存的存储不应该掺杂进业务逻辑中
灵感来源:JAVA开发(通过AOP切面方式管理redis缓存)_redis切面_奋力向前123的博客-CSDN博客
1.引入spring-aspects,fastjson依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
2.新建自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
* @Author
* @Description 新增redis缓存
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AopCacheEnable {
//redis缓存key
String[] key();
//提取缓存时需要转化成的类型
Class<?> resultType();
//redis缓存存活时间默认值(可自定义)
long expireTime() default 3600;
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
* @Description 删除redis缓存注解
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AopCacheEvict {
//redis中的key值
String[] key();
}
3.创建redis工具类
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author Leon
* @Description: TODO
* @date 2021/7/2015:35
*/
@Component
public class RedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 保持指定key-value信息
*
* @param key
* @param value
*/
public void putValue(String key, String value) {
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set(key, value);
System.out.println("Redis存储:key-" + key + ": value-" + value);
}
/**
* 保存key-Object信息
*
* @param key
* @param object
*/
public void putValue(String key, Object object) {
stringRedisTemplate.opsForValue().set(key, JSONObject.toJSONString(object));
System.out.println("Redis存储:key-" + key + ": object-" + object.toString());
}
/**
* 保存Key-value信息,并制定time为失效时间
*
* @param key
* @param value
* @param time 单位为秒
*/
public void putValue(String key, String value, long time) {
stringRedisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
System.out.println("Redis存储:key-" + key + ": value-" + value + " :失效时间为-" + time);
}
/**
* 获取指定key的值
*
* @param key
*/
public String getValue(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 删除指定key对应的信息
*
* @param key
*/
public void deleteValue(String key) {
stringRedisTemplate.delete(key);
}
/**
* 获取指定key的信息,并转化为指定对象
*
* @param key
* @return
*/
public <T> T getValue(String key, Class<T> value) {
return JSONObject.parseObject(stringRedisTemplate.opsForValue().get(key), value);
}
/**
* 更新指定key的失效时间,单位设置为秒
*
* @param key
* @param time
*/
public void updateExpireTime(String key, long time) {
stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
}
public Set<String> getAllKeys(String pattern) {
return stringRedisTemplate.keys(pattern);
}
}
4.创建切面类
import com.alibaba.fastjson.JSONObject;
import com.ctcommon.annotation.AopCacheEnable;
import com.ctcommon.annotation.AopCacheEvict;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
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.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Set;
/*
* @Description 自定义缓存切面具体实现类
**/
@Aspect
@Component
public class CacheEnableAspect {
@Autowired
public RedisService redisService;
/**
* Mapper层切点 使用到了我们定义的 AopCacheEnable 作为切点表达式。
*/
@Pointcut("@annotation(com.ctcommon.annotation.AopCacheEnable)")
public void queryCache() {
}
@Pointcut("@annotation(com.ctcommon.annotation.AopCacheEvict)") // 切点,匹配使用 @AopCacheEvict 注解的方法
public void cacheEvictMethods() {
}
@After("cacheEvictMethods()")
public void cacheEvict(JoinPoint joinPoint) {
AopCacheEvict cacheEvictAnnotation = getCacheEvictAnnotation(joinPoint);
// 获取要删除的缓存键
String cacheKey = cacheEvictAnnotation.key()[0];
// 在这里实现删除 Redis 缓存的逻辑,删除所有包含指定键的缓存
Set<String> allKeys = redisService.getAllKeys(cacheKey);
allKeys.forEach(key->{
redisUtil.deleteValue(key);
});
}
private AopCacheEvict getCacheEvictAnnotation(JoinPoint joinPoint) {
// 获取方法上的 @AopCacheEvict 注解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
return method.getAnnotation(AopCacheEvict.class);
}
@Around("queryCache()")
public Object Interceptor(ProceedingJoinPoint pjp) {
Object result = null;
// 获取方法上的注解
AopCacheEnable aopCacheEnable = getAopCacheEnableAnnotation(pjp);
// 获取方法参数值
Object[] arguments = pjp.getArgs();
//获取class
Class<?> aClass = aopCacheEnable.resultType();
// 生成缓存键
String redisKey = generateCacheKey(aopCacheEnable.key(), arguments);
// 从缓存中获取数据
result = redisService.getValue(redisKey,aClass);
if (result == null) {
// 缓存未命中,执行方法
try {
result = pjp.proceed();
// 将结果存入缓存
if (result != null) {
redisUtil.putValue(redisKey, JSONObject.toJSONString(result),aopCacheEnable.expireTime());
}
} catch (Throwable e) {
e.printStackTrace();
}
}
return result;
}
private AopCacheEnable getAopCacheEnableAnnotation(ProceedingJoinPoint pjp) {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
return method.getAnnotation(AopCacheEnable.class);
}
private String generateCacheKey(String[] key, Object[] arguments) {
StringBuilder keys=new StringBuilder();
keys.append(key[0]);
for (Object argument : arguments) {
if (argument!=null) {
keys.append(argument.toString());
}
}
return keys.toString();
}
}
5.实例
在这个例子中 我们在controller层上的selectProject()方法上加上@AopCacheEnable注解
并给其key值设置为projectList 设置返回值类型为Result.class
此时 在第一次进行查询时 会将key值与参数进行拼接 作为key值存储在redis中
在第二次进行查询时 若参数不变 将会自动从redis获取数据 而不查询数据库
@RequestMapping("/SelectProject")
@AopCacheEnable(key = "projectList",resultType = Result.class)
public Result selectProject(@RequestParam(required = false) Integer id,
@RequestParam(required = false) String proName,
@RequestParam(required = false) Integer size,
@RequestParam(required = false) Integer currentPage){
if (id!=null){
Project byId = projectService.getById(id);
return new Result(byId,200,"获取成功");
}else {
Page<Project> objectPage = new Page<>(currentPage, size);
if (proName==null){
List<Project> records = projectService.page(objectPage).getRecords();
return new Result(records,200,"查询成功");
}else {
LambdaQueryWrapper<Project> eq = new QueryWrapper<Project>().lambda().eq(Project::getName, proName);
List<Project> records = projectService.page(objectPage, eq).getRecords();
return new Result(records,200,"查询成功");
}
}
}
在执行增删改方法时 要确保数据一致性
需要加上@AopCacheEvict注解
并指定key值
随后会执行SpringRedisTemplate方法遍历redis中所有key值
并返回包含所指定key值的set
随后遍历set 删除redis中的包含key的缓存
示例
@AopCacheEvict(key = "project")
public Result addProject(@RequestBody Project project){
System.out.println(project);
project.setStatus(ProjectStatus.CREATED);
projectService.save(project);
return new Result("添加成功");
}
注意:遍历所有包含key的缓存在大型项目时会很耗时间
这里不提供解决方案
文章介绍了如何在Java开发中使用SpringAOP和Fastjson库,通过自定义注解管理Redis缓存,强调了缓存不应直接涉及业务逻辑,并提供了创建切面类和实例化应用的步骤。

869

被折叠的 条评论
为什么被折叠?



