AOP+ 自定义注解 +SpringElExpress自研缓存组件

AOP+ 自定义注解 +SpringElExpress自研缓存组件

背景

思考下这段代码,想想项目中是不是到处存在

先查缓存,缓存里面有,直接返回;缓存没有,查数据库,并更新到缓存

思考:如何将缓存代码从业务代码中剥离,促使业务代码更简洁更易维护,可以把这部分通过AOP来统一处理

前置知识

提前预习一波 SpringEL 表达式

改造代码

首先配置

properties 复制代码
spring.datasource.url=jdbc:mysql://192.168.133.128:3306/wxpay?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath*:mapper/*.xml

spring.data.redis.database=0
spring.data.redis.host=192.168.133.128
spring.data.redis.port=6379
spring.data.redis.password=123456
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1ms
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0

注解和切面定义

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MyRedisCacheAnnotation {
    String keyPrefix();

    String matchValue();
}
java 复制代码
@Slf4j
@Aspect
@Component
public class MyRedisCacheAspect {
    @Resource
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.example.demo.annotation.MyRedisCacheAnnotation)")
    public void cachePointCut() {}

    @Around("cachePointCut()")
    public Object doCache(ProceedingJoinPoint pjp) {
        Object result = null;
        try {
            // 获得重载后的方法名
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            // 确定方法名后获得该方法上面的注解标签 MyRedisCacheAnnotation
            MyRedisCacheAnnotation annotation = method.getAnnotation(MyRedisCacheAnnotation.class);
            // 获取注解上配置的参数
            String keyPrefix = annotation.keyPrefix();
            String matchValue = annotation.matchValue();

            // SpringEL 解析器
            SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
            Expression expression = spelExpressionParser.parseExpression(matchValue); // #id
            StandardEvaluationContext context = new StandardEvaluationContext();
            // 获得方法的形参个数
            Object[] args = pjp.getArgs();
            DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();
            String[] parameterNames = defaultParameterNameDiscoverer.getParameterNames(method);
            for (int i = 0; i < parameterNames.length; i++) {
                log.info("方法参数名称:{}, 值:{}", parameterNames[i], args[i].toString());
                context.setVariable(parameterNames[i], args[i].toString());
            }
            // 拼接 redis 的最终 key 形式
            String key = keyPrefix + ":" + expression.getValue(context).toString();
            log.info("key形式{}", key);
            // 先去 redis 看看有没有
            result = redisTemplate.opsForValue().get(key);
            if (result != null) {
                log.info("redis里有,直接返回了");
                return result;
            }
            result = pjp.proceed();
            if (result != null) {
                redisTemplate.opsForValue().set(key, result);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return result;
    }
}

不要忘了重写 redisTemplate

java 复制代码
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

最后 service 层的效果,简便多了是不是

java 复制代码
@Autowired
private UserMapper userMapper;

@Override
@MyRedisCacheAnnotation(keyPrefix="user", matchValue="#id")
public User getUserById(Integer id) {
    return userMapper.selectById(id);
}
相关推荐
XS030106几秒前
Spring Bean 作用域 & 生命周期
java·后端·spring
NagatoYukee1 分钟前
Spring Security基础部分学习
java·学习·spring
彦为君2 分钟前
JavaSE-07-异常机制
java·开发语言·后端·python·spring
_Aaron___1 小时前
Spring AI 接入 MCP:工具调用不是“能调就行”,关键是边界治理
java·人工智能·spring
辰海Coding1 小时前
MiniSpring框架学习-分解 Dispatcher
java·学习·spring·架构
小江的记录本2 小时前
【JVM虚拟机】JVM调优:常用JVM参数、调优核心指标、OOM排查、GC日志分析、Arthas工具使用(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·后端·python·spring·面试
J2虾虾2 小时前
Spring AI Alibaba文档
java·人工智能·spring
一 乐3 小时前
高校实习信息发布网站|基于Spring Boot的高校实习信息发布网站的设计与实现(源码+数据库+文档)
java·数据库·spring boot·后端·论文·毕设·高校实习信息发布网站
han_hanker4 小时前
BeanUtils.copyProperties 和序列化的问题
java·开发语言·spring boot
西凉的悲伤5 小时前
Spring Boot 中 @Async(value = “alertThreadPool“) 是什么?为什么企业项目喜欢自定义线程池?
spring boot·多线程·async·异步