IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 自定义缓存注解,动态缓存数据 -> 正文阅读

[大数据]自定义缓存注解,动态缓存数据

此注解解决的业务功能:

类似于Cacheable的功能,在方法上添加注解能够动态的缓存起来,只不过我这里自定义了注解,并用redis缓存数据,数据在存储前可以派生其他的类来解决动态的修改自己的缓存业务逻辑。

此注解产生的原因:

线上百万级访问量访问接口,由于各种原因扛不住压力,所以需要解决问题,所以决定采用缓存处理,但是缓存处理因为业务逻辑众多,又不想嵌入具体的代码进行逻辑判断,所以直接利用注解AOP动态判断最后的结果然后对最后的结果进行缓存。但是由于有些数据是不存在的但又要一些空结构来保持前端的需求,才会产生派生类来维持数据机构。

具体代码:

注解?@RedisCacheable


/**
 * redis 动态缓存读取
 * 如果数据不存在则存储返回值到redis
 * 如果redis存在数据,则直接返回
 * attribute key支持# 字符el表达式
 *
 * <code>
 * \@RedisCacheable(key = "#userId",timeout = 60)
 * public User queryUserInfo(Integer userId){
 * ...
 * }
 * </code>
 *
 * @author licl
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCacheable {
    /**
     * redis key
     * # 字符支持 EL表达式
     */
    String key();

    /**
     * 超时时间秒数 second
     */
    long timeout() default -1;

    /**
     * 接口描述
     */
    String describe() default "Interface description";

    /**
     * 自定义处理
     * 用于处理数据存储前的数据格式
     */
    Class<? extends AbstractRedisCacheProccessor> process() default DefaultRedisCacheProccessor.class;
}

定义抽象处理类:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * redisCache注解 自定义处理抽象类
 *
 * @author licl
 */
public abstract class AbstractRedisCacheProccessor {

    /**
     * 前置处理
     *
     * @param obj      process
     * @param redisKey redisKey
     * @return 处理后的process
     */
    abstract Object beforeProcess(Object obj, String redisKey);

    /**
     * 缓存数据
     *
     * @param redisTemplate redisTemplate
     * @param proceed       proceed
     * @param redisKey      redisKey
     * @param timeout       timeout
     */
    public void process(StringRedisTemplate redisTemplate, Object proceed, String redisKey, Long timeout) {
        this.check(proceed);
        redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(proceed, SerializerFeature.WriteMapNullValue), timeout, TimeUnit.SECONDS);
    }

    /**
     * 检查数据是否为空
     *
     * @param obj process
     */
    private void check(Object obj) {
        if (null == obj)
            throw new RuntimeException("data cannot be empty");
    }

}

抽象类默认实现:


import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;

/**
 * 默认实现
 */
public class DefaultRedisCacheProccessor extends AbstractRedisCacheProccessor {
    /**
     * 前置处理
     */
    @Override
    public Object beforeProcess(Object obj, String redisKey) {
        return obj;
    }

    /**
     * 处理
     */
    @Override
    public void process(StringRedisTemplate redisTemplate, Object proceed, String redisKey, Long timeout) {
            super.process(redisTemplate, proceed, redisKey, timeout);
    }

}

我已经把默认处理逻辑删除了,你可以替换成你自己的默认逻辑,然后在新增其他的派生类,集成这个默认类,在具体的派生类里面写处理逻辑。

核心AOP处理类:


import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.log4j.Log4j2;
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.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.ConcurrentHashMap;

/**
 * RedisCacheable aop
 *
 * @author licl
 */
@Log4j2
@Aspect
@Component
public class RedisCacheableAspect {
    /**
     * 实例缓存
     */
    private final static ConcurrentHashMap<String, Object> PROCESS_INSTANCE = new ConcurrentHashMap<>();
    /**
     * 实例方法缓存
     */
    private final static ConcurrentHashMap<String, Method> PROCESS_METHODS = new ConcurrentHashMap<>();

    @Autowired
    private StringRedisTemplate redisTemplate;


    @Pointcut("@annotation(RedisCacheable)")
    private void aroundMethodAspect() {

    }

    @Around(value = "aroundMethodAspect()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RedisCacheable handler = method.getAnnotation(RedisCacheable.class);
//        AssertUtils.isTrue(StringUtils.isNotBlank(handler.key()), "annotation RedisCacheable key cannot be empty");
//        AssertUtils.isTrue(handler.timeout() > 0, "annotation RedisCacheable timeout value must be greater than 0");

        StandardEvaluationContext context = parseElContext(method, joinPoint.getArgs());
        String redisKey = this.getRedisKey(context, handler);
        String result = redisTemplate.opsForValue().get(redisKey);

        if (StringUtils.isNotBlank(result)) {
            log.info("【{}】获取缓存数据 data = {}", handler.describe(), result);
            Type genericReturnType = method.getGenericReturnType();
            if (genericReturnType instanceof ParameterizedType) {
                Class<?> rawType = (Class<?>) ((ParameterizedType) genericReturnType).getRawType();
                return JSON.parseObject(result, rawType);
            }
            return JSON.parseObject(result, Class.forName(genericReturnType.getTypeName()));
        } else {
            Object proceed = joinPoint.proceed();
            if (null != proceed) {
                return process(joinPoint, handler, redisKey);
            }
            return proceed;
        }

    }

    /**
     * 执行处理程序
     */
    private Object process(ProceedingJoinPoint joinPoint, RedisCacheable handler, String redisKey) throws Throwable {
        Object proceed = joinPoint.proceed();
        Class<? extends AbstractRedisCacheProccessor> process = handler.process();

        Object instance = getInstance(process);
        Method beforeProcess = getMethod(process, "beforeProcess", Object.class, String.class);
        Method processMain = getMethod(process, "process", StringRedisTemplate.class, Object.class, String.class, Long.class);

        Object data = beforeProcess.invoke(instance, proceed, redisKey);

        processMain.invoke(instance, redisTemplate, data, redisKey, handler.timeout());

        return data;
    }

    public Method getMethod(Class<? extends AbstractRedisCacheProccessor> process, String methodName, Class<?>... parameterTypes) throws Exception {
        PROCESS_METHODS.clear();
        String name = process.getName();
        //
        String key = name + "." + methodName + "(" + formatParameterName(parameterTypes) + ")";
        Method oldMethod = PROCESS_METHODS.get(key);
        if (null == oldMethod) {
            Method method = process.getMethod(methodName, parameterTypes);
            PROCESS_METHODS.put(key, method);
        }
        return PROCESS_METHODS.get(key);
    }

    public Object getInstance(Class<? extends AbstractRedisCacheProccessor> process) throws Exception {
        String name = process.getName();
        Object instance = PROCESS_INSTANCE.get(name);
        if (null == instance) {
            PROCESS_INSTANCE.put(name, process.newInstance());
        }
        return PROCESS_INSTANCE.get(name);
    }


    public String formatParameterName(Class<?>... parameterTypes) {
        StringBuilder parameter = new StringBuilder(StringUtils.EMPTY);
        if (null == parameterTypes || parameterTypes.length == 0) {
            return parameter.toString();
        }
        for (Class<?> parameterType : parameterTypes) {
            String parameterToString = parameterType.toString();
            parameter.append(parameterToString.replace("class ", StringUtils.EMPTY)).append(",");
        }
        parameter.deleteCharAt(parameter.length() - 1);
        return parameter.toString();
    }


    /**
     * 解析redisKey
     */
    private String getRedisKey(StandardEvaluationContext context, RedisCacheable handler) {
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        String redisKeySpel = handler.key();
        if (redisKeySpel.contains("#")) {
            return parser.parseExpression(redisKeySpel).getValue(context, String.class);
        } else {
            return redisKeySpel;
        }
    }

    /**
     * 内容解析
     */
    private StandardEvaluationContext parseElContext(Method method, Object[] args) {
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = localVariableTable.getParameterNames(method);
        if (null != paraNameArr && paraNameArr.length > 0) {
            //SPEL上下文
            StandardEvaluationContext context = new StandardEvaluationContext();
            //把方法参数放入SPEL上下文中
            for (int i = 0; i < paraNameArr.length; i++) {
                context.setVariable(paraNameArr[i], args[i]);
            }
            return context;
        }
        return null;
    }

}

此代码直接可用。

原理就是扫描注解,然后解析key的 el表达式,然后执行前置缓存处理逻辑,处理完毕后在进行缓存。完全不侵入代码判断。?

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-04-01 23:28:20  更:2022-04-01 23:29:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:21:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码