记叙
好久好久没来了,以后会持续更新,希望多多关注.
ps:迷途的路上记得不要忘了自己 —佛山
前言
redis的火热都是可预见,支持的五种数据结构,性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s,redis的原子操作更是将redis推向了巅峰…
Aop面向切面,横向开发思维,在不影响业务代码的前提下添加配置,实现缓存,权限验证,日志…
功能思路
- 通过注解实现redis缓存
- 通过不同参数,生成不同的key,参数类型可以是基本类型,自定义对象
redis注解(该注解在类上,可自行改成方法上的)
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCache {
/**
* @return 缓存的key值
* 对应的Method的返回值必须 实现 Serializable 接口
*
*/
String key();
/**
* 到分钟
*
*/
int expireTime() default 20;
}
ps:目的是想在contoller层类上使用该注解,缓存get开头的方法的返回数据,这里的key只是前缀,方便操作redis缓存的更新,这里的expireTime的单位是分钟
Aop实现
@Aspect
@Service
@Log4j2
public class RedisAOP {
private static String TYPE = "java.lang.Integer,java.lang.Double," +
"java.lang.Float,java.lang.Long,java.lang.Short," +
"java.lang.Byte,ava.lang.Boolean,java.lang.Char," +
"java.lang.String,int,double,long,short,byte,boolean,char,float";
@Resource
private RedisTemplate redisTemplate;
@Pointcut("@within(cn.stylefeng.guns.sys.modular.consts.RedisCache)")
public void queryCachePointcut() {
}
@Around("queryCachePointcut()")
public Object Interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//类路径名
RedisCache annotation = joinPoint.getTarget().getClass().getAnnotation(RedisCache.class);
//类名
String annotationKey = annotation.key();
int minutes = annotation.expireTime();
//获取方法名
String methodName = signature.getMethod().getName();
String paramValue = getParamValue(joinPoint);
String key = annotationKey + ":" + methodName + "_" + paramValue;
if ((methodName.contains("get") && methodName.substring(0, 3).equalsIgnoreCase("get"))) {
Object data = getObject(minutes, joinPoint, key);
return ResponseData.success(data);
} else if ((methodName.contains("add") && methodName.substring(0, 3).equalsIgnoreCase("add"))
|| (methodName.contains("insert") && methodName.substring(0, 6).equalsIgnoreCase("insert"))
|| (methodName.contains("update")&& methodName.substring(0, 6).equalsIgnoreCase("update"))) {
redisTemplate.delete(redisTemplate.keys(annotationKey + "*"));
}
return joinPoint.proceed();
}
private Object getObject(int minutes, ProceedingJoinPoint joinPoint, String key) throws Throwable {
ValueOperations<String, Object> operations = redisTemplate.opsForValue();
boolean hasKey = redisTemplate.hasKey(key);
Object object = null;
if (hasKey) {
// 缓存中获取到数据,直接返回。
object = operations.get(key);
return object;
}
if (object == null) {
// 缓存中没有数据,调用原始方法查询数据库
object = joinPoint.proceed();
operations.set(key, object, minutes, TimeUnit.MINUTES); // 设置超时时间30分钟
}
return object;
}
/**
* 序列化
*/
@Autowired(required = false)
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisSerializer stringSerializer = new StringRedisSerializer();//序列化为String
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//序列化为Json
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
this.redisTemplate = redisTemplate;
}
private static String getParamValue(JoinPoint joinPoint) {
StringBuilder sb = new StringBuilder();
//获取所有的参数
Object[] args = joinPoint.getArgs();//参数
for (int k = 0; k < args.length; k++) {
Object arg = args[k];
if (arg == null) {
continue;
}
// 获取对象类型
String typeName = arg.getClass().getTypeName();
//如果是基本类型
if (TYPE.contains(typeName)) {
try {
Field[] declaredFields = args.getClass().getDeclaredFields();
String name = declaredFields[0].getName();
Object o = declaredFields[0].get(arg);
if (o != null && !o.equals("")) {
sb.append(name + "_" + o + "_");
}
} catch (Exception e) {
e.printStackTrace();
}
} else {//不是基本类型
//1.获取所有的属性
Field[] fields = arg.getClass().getDeclaredFields();
//遍历每个参数
for (int i = 0; i < fields.length; i++) {
try {
//将私有变公有
fields[i].setAccessible(true);
//获取参数名
String name = fields[i].getName();
//获取值
Object o = fields[i].get(arg);
//如果该参数不为null或者不为空
if (o != null && !o.equals("")) {
sb.append(name + "_" + o + "_");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return sb.toString();
}
}
以下是作者的思路,欢迎聆听
private static String TYPE = "java.lang.Integer,java.lang.Double," +
"java.lang.Float,java.lang.Long,java.lang.Short," +
"java.lang.Byte,ava.lang.Boolean,java.lang.Char," +
"java.lang.String,int,double,long,short,byte,boolean,char,float";
一 ps: 通常一个controller有对象参数和基本类型参数,这里主要用的是字符串来匹配基本类型,不知道有没有更好的方法
@Resource
private RedisTemplate redisTemplate;
@Pointcut("@within(cn.stylefeng.guns.sys.modular.consts.RedisCache)")
public void queryCachePointcut() {
}
二 redis的模板模板方法和切入点不用说了吧
//获取注解上的key值
1. String annotationKey = annotation.key();
//获取参数
2. String paramValue = getParamValue(joinPoint);
3. String key = annotationKey + ":" + methodName + "_" + paramValue;
- 注解上的key(annotationKey )就在这里使用了;
- paramValue详细请看上面的代码,我是先获取该方法上的所有参数,然后进行一个遍历,通过TYPE.contains(typeName) 来区分是不是基本类型,如果是就直接拼接参数,如果是对象,就进行对象属性取值,打到不同参数生成不同的key值
- 这里使用了冒号来分隔,这样做在redis的管理工具中它会以文件的形势储存,比较好管理;
if ((methodName.contains("get") && methodName.substring(0, 3).equalsIgnoreCase("get"))) {
Object data = getObject(minutes, joinPoint, key);
return ResponseData.success(data);
} else if ((methodName.contains("add") && methodName.substring(0, 3).equalsIgnoreCase("add"))
|| (methodName.contains("insert") && methodName.substring(0, 6).equalsIgnoreCase("insert"))
|| (methodName.contains("update")&& methodName.substring(0, 6).equalsIgnoreCase("update"))) {
redisTemplate.delete(redisTemplate.keys(annotationKey + "*"));
}
ps: 这个方法就是判断该方法是不是get开头,如果是get开头的方法就进行一个redis缓存操作,如果是add,insert,update就更下该annotationKey 相关的所有key值打到更新key的作用,当然,你想其他方法名开头或者其他执行方法改这里就可以了,接下来的逻辑就不说了,大家都看得懂
@RedisCache(key = "lvyuanController",expireTime = 1)
public class LvyuanController {
}
具体使用,如果有想用的同学直接复制粘贴就可以使用了,改得地方不多,改下return ResponseData.success(data); 具体的返回值就可以了
思考:适当考虑下性能问题:
1. redis key的定义是在判断方法类型前好,还是方法名判断好?
2.如果改成在方法上的注解怎么改动?
3. 实际应用方向.
import cn.stylefeng.roses.core.reqres.response.ResponseData;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import org.aspectj.lang.reflect.MethodSignature;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
附上aop上的导包,为什么呢 我觉得这个很重要,要减少找包的时间和怀疑包的正确性问题
结语
转载请附上出处,谢谢!
Spring Boot提供了缓存注解@Cacheable、@CacheEvict、@CachePut,没有接触的同学们可以去查询下,我后续也会更新springboot缓存注解的使用
---不要让自大限制了我们的步伐,程序员的心要平静下来,热于学习---
转载:https://blog.csdn.net/ruoyang759976/article/details/105755175