栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring自定义注解实现redis缓存

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring自定义注解实现redis缓存

一、前言

redis是分布式微服务中必用的基础组件之一,现在国内的大部分项目基本上用到,缓存是其主要作用之一,而在项目中频繁使用set()方法添加注解,会造成代码的重复和臃肿,对于开发经验不足的小白,甚至会因为缓存的添加不当直接影响到正常的业务流程,从而酿成事故,因此成熟的公司都会通过封装基础组件,实现通过注解自动添加redis缓存,本文会从原理出发,带领大家亲自实现自定义注解,完成redis缓存的开发,学会了,你可以在同事面前秀一把了。

二、自定义注解的参数说明 @Target:

注解的作用目标,即注解可以使用的位置,通常有
@Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(ElementType.FIELD)——字段、枚举的常量
@Target(ElementType.METHOD)——方法
@Target(ElementType.PARAMETER)——方法参数
@Target(ElementType.CONSTRUCTOR) ——构造函数
@Target(ElementType.LOCAL_VARIABLE)——局部变量
@Target(ElementType.ANNOTATION_TYPE)——注解
@Target(ElementType.PACKAGE)——包

@Retention 注解的保留位置

用来定义注解的生命周期的,并且在使用时需要指定RetentionPolicy,RetentionPolicy有三种策略,分别是:
SOURCE - 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。
CLASS - 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。
RUNTIME - 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

@documented

注解只是用来做标识,没什么实际作用,了解就好。
如果使用@documented标注了,在生成javadoc的时候就会把@documented注解给显示出来。

三、自定义redis注解 JhRedisCache——添加redis缓存的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface JhRedisCache {
    
    String key() default "";
    
    long expire() default 86400L;
    
    Class type() default Object.class;
}
JhRedisCacheEvict——删除redis缓存的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface JhRedisCacheEvict {
    String key() default "";
}
四、自定义AOP切面
@Component
@Aspect
public class RedisCacheAspect {
    private static final String SPEL = "#";
    private static final String KEY_SEPARATOR = "_";
    private static final int TWO = 2;
    private static final Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class);

    private RedisClient redisClient;

    private AppInfo appInfo;

    
    @Around("@annotation(edu.jiahui.redis.starter.annotation.JhRedisCache)")
    private Object handleCache(final ProceedingJoinPoint pjp) throws Throwable {
        // 获取切入的方法对象
        // 这个m是代理对象的,没有包含注解
        Method m = ((MethodSignature) pjp.getSignature()).getMethod();
        // this()返回代理对象,target()返回目标对象,目标对象反射获取的method对象才包含注解
        Method methodWithAnnotations = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(), m.getParameterTypes());
        // 根据目标方法对象获取注解对象
        JhRedisCache cacheAnnotation = methodWithAnnotations.getDeclaredAnnotation(JhRedisCache.class);
        // 解析key
        String keyExpr = cacheAnnotation.key();
        Object[] as = pjp.getArgs();
        String key = getRedisKeyBySpel(keyExpr,methodWithAnnotations, as);
        // 到redis中获取缓存
        String cache = null;
        try {
            cache = redisClient.get(key);
        } catch (Exception e) {
            logger.error("{}查询redis缓存异常:{}",keyExpr,e.getMessage());
        }
        if (StringUtils.isBlank(cache)) {
            // 若不存在,则到数据库中去获取
            Object result = pjp.proceed();
            // 从数据库获取后存入redis,若有指定过期时间,则设置
            try {
                long expireTime = cacheAnnotation.expire();
                if (expireTime > 0) {
                    redisClient.set(key,JSON.toJSONString(result), expireTime, TimeUnit.SECONDS);
                }else{
                    redisClient.set(key, JSON.toJSONString(result));
                }
            } catch (Exception e) {
                logger.warn("{}{}缓存redis异常:{}",keyExpr,e.getMessage(),result);
            }
            return result;
        }
        // 得到被代理的方法上的注解
        Class modelType = cacheAnnotation.type();
        // 得到被代理方法的返回值类型
        Class returnType = ((MethodSignature) pjp.getSignature()).getReturnType();
        // 返回反序列化从缓存中拿到的json
        return deserialize(cache, returnType, modelType);

    }


    @Around("@annotation(edu.jiahui.redis.starter.annotation.JhRedisCacheEvict)")
    private Object handleCacheEvict(ProceedingJoinPoint pjp) throws Throwable {
        // 获取切入的方法对象
        // 这个m是代理对象的,没有包含注解
        Method m = ((MethodSignature) pjp.getSignature()).getMethod();
        // this()返回代理对象,target()返回目标对象,目标对象反射获取的method对象才包含注解
        Method methodWithAnnotations = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(), m.getParameterTypes());
        // 根据目标方法对象获取注解对象
        JhRedisCacheEvict cacheEvictAnnotation = methodWithAnnotations.getDeclaredAnnotation(JhRedisCacheEvict.class);
        // 解析key
        String keyExpr = cacheEvictAnnotation.key();
        Object[] as = pjp.getArgs();
        String key = getRedisKeyBySpel(keyExpr,methodWithAnnotations, as);
        // 先删除数据库中的用户信息再删除缓存
        Object result = pjp.proceed();
        redisClient.delete(key);
        return result;
    }

    public RedisCacheAspect() {
    	// 初始化redisClient对象,不同的项目可能实现不同,此处是结合自己项目中的redis实现的
        appInfo= SpringContext.getBean(AppInfo.class);
        String appName = appInfo.getAppName();
        this.redisClient = SpringContext.getBean(appName, RedisClient.class);
    }

    
    private String getRedisKeyBySpel(String spelExpress, Method method, Object[] params) {
        String redisKey = appInfo.getAppName()+KEY_SEPARATOR+method.getName();
        // 如果为空,则默认服务名_方法名
        if (StringUtils.isBlank(spelExpress)){
            return redisKey;
        }
        // 如果不是spel表达式,则直接使用用户传入的key
        if(!spelExpress.contains(SPEL)){
            return spelExpress;
        }
        // 如果是spel表达式,但是参数为空,则默认服务名_方法名
        if(params==null){
            return redisKey;
        }
        expressionParser parser = new SpelexpressionParser();
        evaluationContext context = new StandardevaluationContext();
        // spel表达式用到的变量,设置第一个参数
        context.setVariable("entity", params[0]);
        // 设置第二个参数
        if(params.length>1&¶ms[1]!=null){
            context.setVariable("entityTwo", params[1]);
        }
        // 设置第三个参数
        if(params.length>TWO&¶ms[TWO]!=null){
            context.setVariable("entityTrd", params[2]);
        }
        // 解析spel表达式
        expression expression = parser.parseexpression(spelExpress, new TemplateParserContext());
        final Object value = expression.getValue(context);
        return redisKey + KEY_SEPARATOR+"_"+Objects.toString(value,"");
    }

    
    private Object deserialize(String json, Class clazz, Class modelType) {
        return clazz.isAssignableFrom(List.class) ? JSON.parseArray(json, modelType) : JSON.parseObject(json, clazz);
    }

}
五、使用案例

简单的使用:

   @JhRedisCache(key = "#{#entity}")
    public TeachCenter selectTeacherCenter(Integer teachCenterId) {
        return teachCenterMapper.selectByPrimaryKey(teachCenterId);
    }

复杂使用:

    @JhRedisCache(key = "#{#entity.getProvinceId()}", type = TeachCenter.class)
    public List selectTeachCenterList(TeachCenterCommonRequest teacherCenterCommonRequest) {
        //获取地区id;
        Integer provinceId = teacherCenterCommonRequest.getProvinceId();
        //分页
        PageHelper.startPage(teacherCenterCommonRequest.getPageNum(), teacherCenterCommonRequest.getPageSize());
        //获取所有教学中心
        List teachCenterList = teachCenterMapper.selectByProvinceId(provinceId);
        return teachCenterList;
}        

本人亲测有效,并已在公司项目中大规模使用,因为依赖redis的配置,这里不再带领大家测试,有兴趣的小伙伴可以在项目中测试看看,欢迎有问题随时沟通。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/322225.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号