Spring Cache抽象

网友投稿 258 2022-09-07


Spring Cache抽象

目录Spring Cache抽象-使用SpEL表达式概述SpEl表达式如何让自定义注解支持SpEL表达式使用方法使用案例1.准备2.自定义注解3.定义AOP拦截注解对方法增强进行读写缓存4.测试

Spring Cache抽象-使用SpEL表达式

概述

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

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

public class UserService {

private Map users = new HashMap();

{

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();

// 通过joinPohttp://int获取被注解方法的形参

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> 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();

JvaZV //调用SpelExpressionParser方法解析出注解的实际值

key = generateKeyBySpEL(spEL, joinPoint);

JvaZV 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 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 {

// 然JvaZV后将读取的结果保存至Redis缓存

boolean resultRow = false;

if (fieldKey.equals("SelectString")) {

//保存的是集合,需要遍历存储

//先类型强转

List objectList = (List) 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

List

for (Video video:videoList) {

video.setBarrages(barrageService.findByVId(video.getvId()));

}

return videoList;

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:python学习笔记|文件操作(python文件的操作方法)
下一篇:【图像融合】基于拉普拉斯金字塔+小波变换实现图像融合含Matlab源码
相关文章

 发表评论

暂时没有评论,来抢沙发吧~