自定义缓存注解
自定义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 相关概念
结合场景理解:
没有使用AOP的时候,我们在业务代码中,需要自己去手动调用缓存的方法,流程如下:
使用AOP之后,我们只需要在业务代码中,添加一个注解,就可以实现缓存的功能,流程如下:
合并简化的流程如下:
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);
}
缓存设置效果测试
连续发两次请求
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?