利用AOP自定义Redis缓存注解

3 篇文章 0 订阅
2 篇文章 0 订阅

背景

在查询类开发中我们有使用缓存的场景,一般可以使用Redis作为缓存,来缓解数据库如MySQL的压力。使用缓存的步骤为:

(1)从Redis缓存中获取数据,如果存在数据,直接返回值。

(2)如果不存在,执行数据库的查询方法

(3)将数据库中的值放入缓存

NO CODE NO BB,代码如下

//a.从缓存中获取
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
    log.info("从缓存中读取到值:{}", value);
    return value;
}

//b.从数据库中查询
List<Member> members = memberMapper.listByName(name);

//c.同步value到缓存
value = JSONArray.toJSONString(members);
redisTemplate.opsForValue().set(key, value, expireTimes, TimeUnit.SECONDS);
return value;

如上代码,这里有个问题,我们只是要做个查询而已,也就是只要b行的代码,其他代码不是业务代码,不应该由开发人员去操心。那我们何不用注解的形式代替a和c代码呢。

使用SpringBoot的缓存注解

SpringBoot提供了现成可用的缓存注解@Cacheble

配置类开启缓存注解

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        ...
     }
     ...
         
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(60))      //缓存过期时间
                .disableCachingNullValues();

        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }
 }

使用@Cacheable注解

@Cacheable(value = "member",key = "#name")
public List<Member> listByName(String name) {
    return memberMapper.listByName(name);
}

测试

@Test
void listByName() {
    String name = "zhouzhou";
    List<Member> members = memberController.listByName(name);
    log.info("members:{}",members);
}

控制台结果

members:[Member(id=1805590839001216, name=zhouzhou, code=109, annotationParam=null)]

上面代码发现使用Spring缓存注解的缓存失效时间还要在配置类中进行配置。于是我在想为什么这个失效时间不做成注解的这一项属性呢,这样自定义失效时间就比较方便了。

自定义缓存注解

注解定义

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomizeCache {

    String key();

    String value();

    long expireTimes() default 120L; //默认过期时间120s

    int semaphoreCount() default Integer.MAX_VALUE;  //默认限制线程并发数
}

参数说明如下:

key():缓存的key,一般是一个动态的参数值

vaule():缓存的value,value::key为Redis缓存中拼接的KEY

expireTimes():缓存失效时间,默认

semaphoreCount():共享锁,并发下允许访问的线程数,用于保护数据库。默认为Integer.MAX_VALUE

AOP注解开发

首先创建切面类

@Component
@Aspect
@Slf4j
public class CacheAspect {
   ...
}

创建横切面,为注解CustomizeCache添加功能。

@Pointcut("@annotation(com.lvshen.demo.redis.cache.CustomizeCache)")
    public void cachePointcut() {
    }

开发缓存功能,定义@Around

 @Around("cachePointcut()")
    public Object doCache(ProceedingJoinPoint point) {
    ...
    }

获取方法上注解的内容

Method method = point.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
CustomizeCache annotation = method.getAnnotation(CustomizeCache.class);
String keyEl = annotation.key();
String prefix = annotation.value();
long expireTimes = annotation.expireTimes();
int semaphoreCount = annotation.semaphoreCount();

解析SpringEL表达式

Object[] args = point.getArgs();
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
    context.setVariable(parameterNames[i], args[i].toString());
}

拼接Redis KEY

//解析
String key = prefix + "::" + expression.getValue(context).toString();

判断缓存中是否存在

value = redisTemplate.opsForValue().get(key);
if (value != null) {
    log.info("从缓存中读取到值:{}", value);
    return value;
}

自定义组件-创建限流令牌

semaphore = new Semaphore(semaphoreCount);
boolean tryAcquire = semaphore.tryAcquire(3000L, TimeUnit.MILLISECONDS);
if (!tryAcquire) {
    //log.info("当前线程【{}】获取令牌失败,等待其他线程释放令牌",   Thread.currentThread().getName());
    throw new RuntimeException(String.format("当前线程【%s】获取令牌失败,等带其他线程释放令牌", Thread.currentThread().getName()));
}

如果缓存没有数据,则执行原本方法。

 value = point.proceed();

同步value到缓存

redisTemplate.opsForValue().set(key, value, expireTimes, TimeUnit.SECONDS);

最后释放令牌

} finally {
    if (semaphore == null) {
     return value;
    } else {
     semaphore.release();
    }
}

调用注解

@CustomizeCache(value = "member", key = "#name")
public List<Member> listByNameSelfCache(String name) {
 return memberMapper.listByName(name);
}

测试

@Test
void testCache() {
    String name = "lvshen99";
    List<Member> members = memberService.listByNameSelfCache(name);
    log.info("members:{}",members);
}

测试结果

members:[Member(id=15, name=lvshen99, code=200, annotationParam=null)]

Redis上显示

我们也可以自定义缓存失效时间,如设置失效时间

 @CustomizeCache(value = "member", key = "#name",expireTimes = 3600)
 public List<Member> listByNameSelfCache(String name) {
  return memberMapper.listByName(name);
 }

失效时间为3600ms,如图,显示3585ms是因为截图的时候过了15ms。

源码地址如下:

完整源码Github地址:https://github.com/lvshen9/demo/tree/lvshen-dev/src/main/java/com/lvshen/demo/redis/cache

往期推荐

扫码二维码,获取更多精彩。或微信搜Lvshen_9,可后台回复获取资料

1.回复"java" 获取java电子书;

2.回复"python"获取python电子书;

3.回复"算法"获取算法电子书;

4.回复"大数据"获取大数据电子书;

5.回复"spring"获取SpringBoot的学习视频。

6.回复"面试"获取一线大厂面试资料

7.回复"进阶之路"获取Java进阶之路的思维导图

8.回复"手册"获取阿里巴巴Java开发手册(嵩山终极版)

9.回复"总结"获取Java后端面试经验总结PDF版

10.回复"Redis"获取Redis命令手册,和Redis专项面试习题(PDF)

11.回复"并发导图"获取Java并发编程思维导图(xmind终极版)

另:点击【我的福利】有更多惊喜哦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值