自定义缓存注解

自定义Springboot缓存注解

在使用redis缓存时,我们可能使用Jedis,RedisTemplate或者使用@Cacheable注解。尽管这些方法都能够实现缓存的功能,但是有时在真实的业务当中这些方法可能还不够简洁和灵活,于是我们可以自定义缓存注解来解决问题。

SpringBoot 中自定义注解的格式

在Spring Boot中,自定义注解的格式与使用预定义注解类似,需要遵循以下步骤:

  • @interface
  • @Retention
  • @Target
    定义注解:使用@interface关键字定义一个新的注解,指定注解的名称、属性等信息。例如:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomAnnotation {
    String value() default "";
    int count() default 0;
}

在上述示例中,CustomAnnotation是自定义注解的名称,value()和count()是注解的属性,可以根据需要自定义其他属性。

使用自定义注解:在需要应用自定义注解的地方使用注解,并提供相应的属性值。例如:

@RestController
@RequestMapping("/api")
public class UserController {

    @CustomAnnotation(value = "custom", count = 5)
    @GetMapping("/users")
    public List<User> getUsers() {
        return userService.getAllUsers();
    }

    // ...
}

在上述示例中,@CustomAnnotation注解应用在getUsers()方法上,并设置了注解的属性值。

处理自定义注解:根据需要,编写相应的处理逻辑来处理自定义注解。可以使用反射等方式来获取注解的属性值,并执行相应的逻辑。
需要注意的是,自定义注解需要指定注解的保留策略(@Retention)和目标范围(@Target)。在示例中,@Retention(RetentionPolicy.RUNTIME)表示注解在运行时仍然可用,@Target(ElementType.METHOD)表示注解可应用于方法。

通过以上步骤,您可以在Spring Boot应用中定义和使用自定义注解,并根据需要编写相应的处理逻辑。

自定义缓存注解

AOP 相关概念

img
结合场景理解:
没有使用AOP的时候,我们在业务代码中,需要自己去手动调用缓存的方法,流程如下:

img
使用AOP之后,我们只需要在业务代码中,添加一个注解,就可以实现缓存的功能,流程如下:
img

合并简化的流程如下:
img

Spring EL表达式

Spring EL表达式是Spring框架中的一种表达式语言,用于在运行时查询和操作对象图。Spring EL表达式的语法与Java类似,下面是一个简单的SpringBoot 测试用例:

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    public static void main(String[] args) {
        // 创建ExpressionParser对象
        ExpressionParser parser = new SpelExpressionParser();

        // 创建ExpressionContext对象
        StandardEvaluationContext context = new StandardEvaluationContext();

        // 添加变量到ExpressionContext中
        context.setVariable("name", "Alice");

        // 定义字符串表达式,使用变量name
        String expression = "'Hello, ' + #name";

        // 解析表达式
        Expression exp = parser.parseExpression(expression);

        // 获取表达式结果,指定ExpressionContext作为上下文环境
        String result = exp.getValue(context, String.class);

        // 打印结果
        System.out.println(result);
    }
}

自定义缓存注解详解

首先定义自己的注解,@Retention(RetentionPolicy.RUNTIME)表示注解在运行时仍然可用,@Target(ElementType.METHOD)表示注解可应用于方法


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

    String cacheName();

    String key();

    int expireInSeconds() default 0;

}

然后定义一个切面类,用于处理自定义注解,这里使用了Spring EL表达式来获取注解的属性值,然后调用缓存方法,最后返回缓存结果。


@Component
@Aspect
public class MyCacheAspect {

    private static final Logger logger = LoggerFactory.getLogger(MyCacheAspect.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private GoodsMapper goodsMapper;



    @Around("@annotation(myCache)")
    public Object odAround(ProceedingJoinPoint joinPoint, MyCache myCache) throws Throwable {

        // 获取缓存Key
        String cacheKey = getCacheKey(joinPoint, myCache);

        // 判断缓存是否存在
        Object value = redisTemplate.opsForValue().get(cacheKey);
        if(value != null){
            logger.info("缓存命中,直接返回 key:{}, value:{}", cacheKey, value);
            return value;
        }

        // 缓存不存在,查询数据库
        value = joinPoint.proceed();
        logger.info("缓存未命中,查询数据库 key:{}, value:{}", cacheKey, value);
        if(myCache.expireInSeconds() > 0){
            redisTemplate.opsForValue().set(cacheKey, value, myCache.expireInSeconds(), TimeUnit.SECONDS);
        }else{
            redisTemplate.opsForValue().set(cacheKey, value);
        }
        return value;
    }

    private  String getCacheKey(ProceedingJoinPoint joinPoint, MyCache myCache) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(myCache.key());
        EvaluationContext context = new StandardEvaluationContext();

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(methodSignature.getMethod());
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        // 获取表达式的值 mall:goods:1
        String cacheKey = myCache.cacheName() + expression.getValue(context);
        return cacheKey;
    }
}


然后再Service层的方法上加上自定义注解,就可以实现缓存功能了。最后注意在启动类上加上@EnableAspectJAutoProxy注解,开启AOP功能。

    @MyCache(cacheName = Constants.GOODS_CACHE_KEY_PREFIX, key = "#id")
    @Override
    public GoodsDTO getGoods(Long id) {

        GoodsDO goodsDO = goodsMapper.selectGoodsById(id);
        Assert.notNull(goodsDO);
        return ObjectTransformer.transform(goodsDO, GoodsDTO.class);
    }

缓存设置效果测试

连续发两次请求
img

posted @   末日旅行家  阅读(148)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示