AOP+ 自定义注解 +SpringElExpress自研缓存组件
- 背景
- 前置知识
- 改造代码
背景
思考下这段代码,想想项目中是不是到处存在
先查缓存,缓存里面有,直接返回;缓存没有,查数据库,并更新到缓存
思考:如何将缓存代码从业务代码中剥离,促使业务代码更简洁更易维护,可以把这部分通过AOP来统一处理
前置知识
提前预习一波 SpringEL 表达式
改造代码
首先配置
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
注解和切面定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MyRedisCacheAnnotation {
String keyPrefix();
String matchValue();
}
@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
@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 层的效果,简便多了是不是
@Autowired
private UserMapper userMapper;
@Override
@MyRedisCacheAnnotation(keyPrefix="user", matchValue="#id")
public User getUserById(Integer id) {
return userMapper.selectById(id);
}