Spring Cache抽象-使用SpEL表达式解析

2023-01-21 0 1,387

目录

Spring Cache抽象-使用SpEL表达式

概述

在Spring Cache注解属性中(比如key,condition和unless),Spring的缓存抽象使用了SpEl表达式,从而提供了属性值的动态生成及足够的灵活性。

下面的代码根据用户的userCode进行缓存,对于key属性,使用了表达式自定义键的生成。

public class UserService {
    private Map<Integer, User> users = new HashMap<Integer, User>();
    {
        users.put(1, new User(\"1\", \"w1\",37));
        users.put(2, new User(\"2\", \"w2\", 34));
    }
    @Cacheable(value = \"users\", key = \"#user.userCode\" condition = \"#user.age < 35\")
    public User getUser(User user) {
        System.out.println(\"User with id \" + user.getUserId() + \" requested.\");
        return users.get(Integer.valueOf(user.getUserId()));
    }

SpEl表达式

SpEL表达式可基于上下文并通过使用缓存抽象,提供与root独享相关联的缓存特定的内置参数。

名称 位置 描述 示例
methodName root对象 当前被调用的方法 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

如何让自定义注解支持SpEL表达式

  • SpEL:即Spring Expression Language,是一种强大的表达式语言。在Spring产品组合中,它是表达式计算的基础。它支持在运行时查询和操作对象图,它可以与基于XML和基于注解的Spring配置还有bean定义一起使用。由于它能够在运行时动态分配值,因此可以为我们节省大量Java代码。可以用于解析特殊字符串(比如Bean的属性可以直接在字符串中的点出来)。
  • SpEL的应用:常见的应用,如注解上的使用,Spring缓存中使用的注解
    @cachable(key=\"#user.uId\")
    public User createUser(User user) {
     return user;
    }

SpEL还可以用在xml等等上面的解析,大家可以去查阅相关资料。本文主要介绍如果将SpEl与自定义注解相结合,从而解析出自定义注解value的实际值。

  • Spring缓存操作起来非常方便,只需要加上注解便可实现,Spring也提供的CacheManager,使用者可以配置Redis使用Redis缓存。Spring注解也支持自定义的Key命名,功能已经挺齐全了。
  • 但是,如果想要更多的自定义缓存数据存储格式,比如说缓存的数据之间是有层次关系的(比如视频稿件包含视频,视频下面又包含了视频弹幕),此时想要在更新最顶层的视频弹幕时,不删除整个缓存,而只是更新某个视频稿件下的某个视频的这一条视频弹幕,Spring提供的缓存注解似乎有点不够用。
  • 此时有的开发者可能会想到使用自定义注解+AOP+Jedis来更加细分缓存的存储结构,但是又想用到强大的SpEL表达式来为自定义注解的值赋值(不使用SpEL的话,需要在AOP中获取入参或者返回值,但是每个方法的数据类型又不相同,想要拿到特定的值,便需要类型判断-转换),此时便可以使用SpEL提供的SpelExpressionParser工具来进行解析注解的值,使用十分方便,只需按照SpEL的规则(#)来书写即可。

使用方法

generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint)方法封装了SpelExpressionParser解析SpEL的方法,使用时只需要传入spELString:注解的值以及AOP的 joinPoint即可,SpelExpressionParser便会自动的为我们解析出注解的实际值

     /**
     * 用于SpEL表达式解析.
     */
    private SpelExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于获取方法参数定义名字.
     */
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
    
    public String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) {
        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        // 解析过后的Spring表达式对象
        Expression expression = parser.parseExpression(spELString);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        // 给上下文赋值
        for(int i = 0 ; i < args.length ; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        // 表达式从上下文中计算出实际参数值
        /*如:
            @annotation(key=\"#student.name\")
             method(Student student)
             那么就可以解析出方法形参的某属性值,return “xiaoming”;
          */
        return expression.getValue(context).toString();
    }

使用案例

1.准备

①.SpringAop相关jar包,

②.Spring-expression

2.自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelectRedisCache {
    String key(); //Redis缓存的HK
    String fieldKey() ; //Redis缓存的K
    //默认十个小时清空
    int expireTime() default 36000;
}

3.定义AOP拦截注解对方法增强进行读写缓存

@Aspect
public class SelectRedisCacheAop extends SPELUtil {
 private Map<String,Map<String,Object>> redisMap = new HashMap();
    @Around(\"execution(@com.XliXli.annotation.consuRedisCache.SelectRedisCache * *.*(..)) && @annotation(cacheable)\")
    public Object aroundCacheable(ProceedingJoinPoint joinPoint, SelectRedisCache cacheable) throws Throwable {
        //首先获取注解的实际值,如果是SpEl表达式则进行解析
        String key = \"\";
        String fieldKey = \"\";
        Object redisObj = null;
        try {
            if (!cacheable.key().contains(\"#\")) {
                //注解的值非SPEL表达式,直接解析就好
                key = cacheable.key();
            } else {
                //使用注解中的key, 支持SpEL表达式
                String spEL = cacheable.key();
                //调用SpelExpressionParser方法解析出注解的实际值
                key = generateKeyBySpEL(spEL, joinPoint);
                System.out.println(\"key=\" +key);
            }
            //获取fieldKey,同上面的key一样
            if (cacheable.fieldKey().equals(\"\")) {
                //等于空,则查询整个大Key
                fieldKey = \"SelectString\";
            } else {
                //使用注解中的key, 支持SpEL表达式
                String spEL = cacheable.fieldKey();
                fieldKey = generateKeyBySpEL(spEL, joinPoint);
            }
   //如果注解的fieldKey值为\"\",则查询大Key
            if (fieldKey.equals(\"SelectString\")) {
                //直接查询的是大Key
                Set keys = redisMap.get(key).keySet();
                //使用集合来接收查询出来的对象
                List<Object>  redisList = new ArrayList<>();
                //遍历缓存fieldKey,查出缓存中每一个对象,放入redisList中
                for (Object fieldKey2 : keys) {
                    Object innerObj = redisMap.get(key).get(fieldKey2);
                    redisList.add(innerObj);
                }
                redisObj = redisList;
                if (redisList == null || redisList.size() <1) {
                    redisObj = null;
                }
            } else {
            //否则,查询的是单个对象
                redisObj =redisMap.get(key).get(fieldKey);
            }
            if (redisObj !=null) {
                return redisObj;
            }
        }catch (Exception e) {
            Exception e2 = new Exception(\"查询不到缓存异常\");
            e.printStackTrace();
            e2.printStackTrace();
        }
        //以上,是使用AOP拦截查询方法,如果缓存中存在,则直接返回缓存结果,
        //减少数据库查询压力。
        //没有缓存则读取MySQL
        System.out.println(\"查询不到缓存\");
        //执行方法
        Object resultOld = joinPoint.proceed();
        //查询结果不为空,则存入缓存,便于下次直接从缓存中查询数据
        if (resultOld != null) {
            try {
                //        然后将读取的结果保存至Redis缓存
                boolean resultRow = false;
                if (fieldKey.equals(\"SelectString\")) {
                    //保存的是集合,需要遍历存储
                    //先类型强转
                    List<Object> objectList = (List<Object>) resultOld;
                    //遍历返回值集合,进行缓存
                    for (Object o : objectList) {
                    //由于不同对象存储缓存时,使用的key、fieldKey都不相同,
                    //本次模拟都是以数据表的主键值作为fieldKey来存储,然后用不同的key作为区分。
                    //因此需要进行类型转换来获取每个不同对象的不同主键调用方法。
                    //当然,如果你所有的对象获取主键的方法名都一样的话,
                    //完全可以使用反射中的【使用方法名获取方法】来调用对象返回主键值。
                        if (o instanceof Barrage) { //缓存弹幕对象
                            Barrage barrageO = (Barrage) o;
                            fieldKey = barrageO.getBaId() + \"\";
                            //增加单个
                            redisMap.put(key, new HashMap<>().put(fieldKey,barrageO ))
                         
                        } else if (o instanceof Video){//缓存视频对象
                            Video videoO = (Video) o;
                            fieldKey = barrageO.getvId() + \"\";
                            //增加单个
                            redisMap.put(key, new HashMap<>().put(fieldKey,videoO ))
                        } else {
                            //TODO 继续增
                        }
                    }
                } else {
                    //增加单个
                    redisTemplate.opsForHash().put(key, fieldKey, resultOld);
                }
            } catch (Exception e) {
                Exception e2 = new Exception(\"查询后添加缓存异常\");
                e.printStackTrace();
                e2.printStackTrace();
            }
        }
        return resultOld;
    }
        public String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
        Expression expression = parser.parseExpression(spELString);
        EvaluationContext context = new StandardEvaluationContext();
        Object[] args = joinPoint.getArgs();
        for(int i = 0 ; i < args.length ; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context).toString();
    }
}

4.测试

缓存视频稿件:存储数据格式为:

  • VideoByVSIdXXX - VideoId-Video
  • VideoByVSId+视频稿件Id—视频Id—视频
 @SelectRedisCache(key = \"\'VideoByVSId\' + #V_OriginId\", fieldKey = \"\")
    public List<Video> findByOrigin(Long V_OriginType, Long V_OriginId) {
        List<Video> videoList = videoMapper.selectList(new EntityWrapper<Video>().eq(\"V_OriginType\",V_OriginType).eq(\"V_OriginId\",V_OriginId));
        for (Video video:videoList) {
            video.setBarrages(barrageService.findByVId(video.getvId()));
        }
        return videoList;
    }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

:本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可, 转载请附上原文出处链接。
1、本站提供的源码不保证资源的完整性以及安全性,不附带任何技术服务!
2、本站提供的模板、软件工具等其他资源,均不包含技术服务,请大家谅解!
3、本站提供的资源仅供下载者参考学习,请勿用于任何商业用途,请24小时内删除!
4、如需商用,请购买正版,由于未及时购买正版发生的侵权行为,与本站无关。
5、本站部分资源存放于百度网盘或其他网盘中,请提前注册好百度网盘账号,下载安装百度网盘客户端或其他网盘客户端进行下载;
6、本站部分资源文件是经压缩后的,请下载后安装解压软件,推荐使用WinRAR和7-Zip解压软件。
7、如果本站提供的资源侵犯到了您的权益,请邮件联系: 442469558@qq.com 进行处理!

猪小侠源码-最新源码下载平台 Java教程 Spring Cache抽象-使用SpEL表达式解析 http://www.20zxx.cn/463194/xuexijiaocheng/javajc.html

猪小侠源码,优质资源分享网

常见问题
  • 本站所有资源版权均属于原作者所有,均只能用于参考学习,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担
查看详情
  • 最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,建议提前注册好百度网盘账号,使用百度网盘客户端下载
查看详情

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务