需求背景:在使用springbot cache时,发现@cacheabe不能设置缓存时间,导致生成的缓存始终在redis中。

环境:springboot 2.1.5 + redis

解决办法:利用AOP自定义注解,用SPEL来解释key表达式。

1.定义注解

package com.test.entity.util.annotation.cache;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCacheable { /**
* 缓存key
*
* @return
*/
String key(); /**
* 是否缓存空值
*
* @return
*/
boolean cacheNull() default false; /**
* 生存时间,单位是秒,默认为-1(永不过期)
*
* @return
*/
int ttl() default -1; /**
* 生存状态
*
* true:每访问一次,将刷新存活时间
*
* false:不刷新存活时间,时间一到就清除
*
* @return
*/
boolean state() default true;
}

2.实现AOP

package com.test.service.aop;

import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.context.annotation.Lazy;
import org.springframework.expression.Expression;
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 com.test.entity.util.annotation.cache.MyCacheable;
import com.test.util.redis.RedisUtil; @Aspect
@Component
@Lazy(false)
public class AspectCacheable { private Logger log = LoggerFactory.getLogger(AspectCacheable.class); @Autowired
private RedisUtil redisUtil; /**
* 定义切入点
*/
@Pointcut("@annotation(com.test.entity.util.annotation.cache.MyCacheable)")
private void cut() {
// do nothing
} /**
* 环绕通知
*/
@Around("cut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 读取缓存注解
MyCacheable myCacheable = this.getMethodAnnotation(joinPoint);
// 读取类注解
CacheConfig cacheConfig = this.getClassAnnotation(joinPoint);
// 获取方法传入参数
Object[] params = joinPoint.getArgs();
// 获得解释之后的key
String strKey = this.getKey(cacheConfig, myCacheable, params);
log.debug("解释之后的key:{}", strKey);
// 在方法执行前判断是否存在缓存
Object object = this.getCache(strKey, myCacheable.state(), myCacheable.ttl());
if (object == null) {
// 创建缓存
object = this.createCache(joinPoint, strKey, myCacheable);
}
return object;
} /**
* 获取方法中声明的注解
*
* @param joinPoint
* @return
* @throws NoSuchMethodException
*/
private MyCacheable getMethodAnnotation(JoinPoint joinPoint) throws NoSuchMethodException {
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 反射获取目标类
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法对应的参数类型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根据类、方法、参数类型(重载)获取到方法的具体信息
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
// 拿到方法定义的注解信息
return objMethod.getDeclaredAnnotation(MyCacheable.class);
} /**
* 获取类中声明的注解
*
* @param joinPoint
* @return
* @throws NoSuchMethodException
*/
private CacheConfig getClassAnnotation(JoinPoint joinPoint) throws NoSuchMethodException {
// 反射获取目标类
Class<?> targetClass = joinPoint.getTarget().getClass();
return targetClass.getDeclaredAnnotation(CacheConfig.class);
} /**
* 读取现有缓存
*
* @param key
* 实际key,非key表达式
* @param state
* 是否刷新存活时间
* @return
*/
private Object getCache(String key, boolean state, int ttl) {
Object obj = redisUtil.get(key);
if (obj != null && state && ttl != -1) {
// 存在缓存&每次访问重置TTL&非永不过期
// 每次访问后重新刷新TTL,还原为原来值
redisUtil.expire(key, ttl);
}
return obj;
} /**
* 解析key表达式,得到实际的key
*
* @param myCacheable
* @param params
* @return
*/
private String getKey(CacheConfig cacheConfig, MyCacheable myCacheable, Object[] params) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
// 获得原始key的表达式
String strSourceKey = myCacheable.key();
int intSeq = -1;
String strSearchSeq = null;
int intStartPos = 0;
// 用SPEL解析表达式
while (++intSeq < params.length) {
strSearchSeq = "#p" + intSeq;
intStartPos = StringUtils.indexOf(strSourceKey, strSearchSeq, intStartPos);
if (intStartPos < 0) {
break;
} else {
ctx.setVariable("p" + intSeq, params[intSeq]);
}
}
// 执行表达式
Expression expression = parser.parseExpression(strSourceKey);
String strKey = expression.getValue(ctx).toString();
// 拼接上缓存名称,spring cache会加上前缀,是在CacheConfig中配置的。
if (cacheConfig != null) {
strKey = cacheConfig.cacheNames()[0] + ":" + strKey;
}
return strKey;
} /**
* 创建缓存
*
* @param joinPoint
* @param strKey
* @param myCacheable
* @return
* @throws Throwable
*/
private Object createCache(ProceedingJoinPoint joinPoint, String strKey, MyCacheable myCacheable) throws Throwable {
// 没有缓存则执行目标方法
// 获取目标方法的名称
String methodName = joinPoint.getSignature().getName();
log.debug("目标执行方法:{}", methodName);
// 执行源方法
Object object = joinPoint.proceed();
if (object != null) {
// 设置缓存
redisUtil.set(strKey, object);
redisUtil.expire(strKey, myCacheable.ttl());
} else {
// 判断是否缓存null
if (myCacheable.cacheNull()) {
redisUtil.set(strKey, object);
}
}
return object;
}
}

3.在类上应用注解

@CacheConfig(cacheNames = "coach")

@Service
@Transactional
public class ServiceImplCoach implements ServiceCoach { private Logger log = LoggerFactory.getLogger(ServiceImplCoach.class); @Autowired
private DaoCoach daoCoach;

@MyCacheable(key = "'coachnum:'+#p0", ttl = 3600, state = false)

  @Override
public EntityCoach select(String coachnum) {
EntityCoach entityCoach = null;
if (StringUtils.isNotBlank(coachnum)) {
try {
entityCoach = daoCoach.selectByPrimaryKey(coachnum);
} catch (Exception e) {
log.error("查询教练员发生错误:{}", e);
}
} else {
log.info("查询教练员,输入不符合要求");
}
return entityCoach;
} @CacheEvict(key = "'coachnum:'+#p0")
@Override
public EntityRes delete(String coachnum) throws Exception {
EntityRes entityRes = new EntityRes();
log.debug("删除教练员,id={}", coachnum);
if (StringUtils.isBlank(coachnum)) {
log.info("删除教练员,输入不符合要求。");
entityRes.setErrorcode(INVALID);
} else {
daoCoach.deleteByPrimaryKey(coachnum);
entityRes.setErrorcode(CODE_SUCC);
}
return entityRes;
} }

RedisUtil 是 redis操作公共类,大家可以用自己的。

springboot:自定义缓存注解,实现生存时间需求的更多相关文章

  1. SpringBoot自定义Condition注解

        最近碰到个这样的需求,需要同一套代码适配个版本数据库(数据库不同,且部分表的字段及关联关系可能会不同),即这套代码配置不同的数据库都能跑.项目采用的框架为SpringBoot+Mybatis. ...

  2. SpringBoot开启缓存注解

    https://blog.csdn.net/sanjay_f/article/details/47372967 https://www.cnblogs.com/lic309/p/4072848.htm ...

  3. SpringBoot Redis缓存 @Cacheable、@CacheEvict、@CachePut

    文章来源 https://blog.csdn.net/u010588262/article/details/81003493 1. pom.xml <dependency> <gro ...

  4. Spring之缓存注解@Cacheable

    https://www.cnblogs.com/fashflying/p/6908028.html https://blog.csdn.net/syani/article/details/522399 ...

  5. 详解Spring缓存注解@Cacheable,@CachePut , @CacheEvict使用

    https://blog.csdn.net/u012240455/article/details/80844361 注释介绍 @Cacheable @Cacheable 的作用 主要针对方法配置,能够 ...

  6. [技术博客] SPRINGBOOT自定义注解

    SPRINGBOOT自定义注解 在springboot中,有各种各样的注解,这些注解能够简化我们的配置,提高开发效率.一般来说,springboot提供的注解已经佷丰富了,但如果我们想针对某个特定情景 ...

  7. 更加灵活的参数校验,Spring-boot自定义参数校验注解

    上文我们讨论了如何使用@Min.@Max等注解进行参数校验,主要是针对基本数据类型和级联对象进行参数校验的演示,但是在实际中我们往往需要更为复杂的校验规则,比如注册用户的密码和确认密码进行校验,这个时 ...

  8. springboot + 拦截器 + 注解 实现自定义权限验证

    springboot + 拦截器 + 注解 实现自定义权限验证最近用到一种前端模板技术:jtwig,在权限控制上没有用springSecurity.因此用拦截器和注解结合实现了权限控制. 1.1 定义 ...

  9. Springboot中使用自定义参数注解获取 token 中用户数据

    使用自定义参数注解获取 token 中User数据 使用背景 在springboot项目开发中需要从token中获取用户信息时通常的方式要经历几个步骤 拦截器中截获token TokenUtil工具类 ...

随机推荐

  1. Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'org_mer_id' in where clause is ambiguous

    ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolatio ...

  2. 项目Beta冲刺(团队6/7)

    项目Beta冲刺(团队) --6/7 作业要求: 项目Beta冲刺(团队) 1.团队信息 团队名 :男上加男 成员信息 : 队员学号 队员姓名 个人博客地址 备注 221600427 Alicesft ...

  3. 前端性能----CDN

    Content Distribute Network(内容分发网络)是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡.内容分发.调度等功能模块,使用户就近获取所需内容 ...

  4. reflow和repaint理解总结

    repaint就是重绘,reflow就是回流 严重性: 在性能优先的前提下,reflow的性能消耗要比repaint的大. 体现: repaint是某个dom元素进行重绘,reflow是整个页面进行重 ...

  5. 将表格转化为Latex代码的在线工具

    这个在线工具的网址为:http://www.tablesgenerator.com/latex_tables,好用.

  6. 跟UI自动化测试有关的技术

    大家都知道,针对UI的自动化技术一般要支持下列的东西: 1. 识别窗口   能够识别尽量多的窗口种类,支持尽量多的UI技术.比如Win32.WinForm.WPF以及WebPage(这个比较特殊,确切 ...

  7. Windows UI自动化测试的XPATH实现 - WPATH

    https://segmentfault.com/a/1190000010339021 从事Windows 桌面应用自动化测试也有一些年了,现在谈这个话题并不流行.因为除了企业级应用,很少有公司会只选 ...

  8. IDEA创建Mybatis的配置文件---sqlMapConfig.xml

    Mybatis的配置文件不像Spring的配置文件,在Maven当中添加过依赖之后就可以在下面这个地方打开,需要自己去手动去编写配置文件,但是自己编写的话会记不住要引入的DTD,所以就需要自己创建一个 ...

  9. 基于Docker方式实现Elasticsearch集群

    采用docker容器,搭建两个es集群,可根据步骤自行扩展n+集群 1.创建es挂载目录 cd /usr/localmkdir -p es/config 2.创建es存放数据目录 cd esmkdir ...

  10. LeetCode 911. Online Election

    原题链接在这里:https://leetcode.com/problems/online-election/ 题目: In an election, the i-th vote was cast fo ...