小言_互联网的博客

【java加强】redis---aop

397人阅读  评论(0)

记叙

好久好久没来了,以后会持续更新,希望多多关注.
ps:迷途的路上记得不要忘了自己 —佛山

前言

redis的火热都是可预见,支持的五种数据结构,性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s,redis的原子操作更是将redis推向了巅峰…
Aop面向切面,横向开发思维,在不影响业务代码的前提下添加配置,实现缓存,权限验证,日志…

功能思路

  1. 通过注解实现redis缓存
  2. 通过不同参数,生成不同的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;
  1. 注解上的key(annotationKey )就在这里使用了;
  2. paramValue详细请看上面的代码,我是先获取该方法上的所有参数,然后进行一个遍历,通过TYPE.contains(typeName) 来区分是不是基本类型,如果是就直接拼接参数,如果是对象,就进行对象属性取值,打到不同参数生成不同的key值
  3. 这里使用了冒号来分隔,这样做在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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场