使用热插拔aop+反射+redis自定义注解+spring EL表达式打造redis缓存组件,优雅重构缓存代码
redis配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableAspectJAutoProxy //V2 开启AOP自动代理
public class RedisConfig
{
/**
* @param lettuceConnectionFactory
* @return
*
* redis序列化的工具配置类,下面这个请一定开启配置
* 127.0.0.1:6379> keys *
* 1) "ord:102" 序列化过
* 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
{
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRedisCache //@EnableAspectJAutoProxy //启AOP自动代理
{
//约等于键的前缀prefix,
String keyPrefix();
//SpringEL表达式,解析占位符对应的匹配value值
String matchValue();
}
AOP
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
import java.util.Objects;
@Component
@Aspect
public class MyRedisCacheAspect
{
@Resource
private RedisTemplate redisTemplate;
//配置织入点
@Pointcut("@annotation(com.atguigu.interview2.annotations.MyRedisCache)")
public void cachePointCut(){}
@Around("cachePointCut()")
public Object doCache(ProceedingJoinPoint joinPoint)
{
Object result = null;
/**
* @MyRedisCache(keyPrefix = "user",matchValue = "#id")
* public User getUserById(Integer id)
* {
* return userMapper.selectByPrimaryKey(id);
* }
*/
try
{
//1 获得重载后的方法名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//2 确定方法名后获得该方法上面配置的注解标签MyRedisCache
MyRedisCache myRedisCacheAnnotation = method.getAnnotation(MyRedisCache.class);
//3 拿到了MyRedisCache这个注解标签,获得该注解上面配置的参数进行封装和调用
String keyPrefix = myRedisCacheAnnotation.keyPrefix();
String matchValueSpringEL = myRedisCacheAnnotation.matchValue();
//4 SpringEL 解析器
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(matchValueSpringEL);//#id
EvaluationContext context = new StandardEvaluationContext();
//5 获得方法里面的形参个数
Object[] args = joinPoint.getArgs();
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++)
{
System.out.println("获得方法里参数名和值: "+parameterNames[i] + "\t" + args[i].toString());
context.setVariable(parameterNames[i], args[i].toString());
}
//6 通过上述,拼接redis的最终key形式
String key = keyPrefix + ":" + expression.getValue(context).toString();
System.out.println("------拼接redis的最终key形式: " + key);
//7 先去redis里面查询看有没有
result = redisTemplate.opsForValue().get(key);
if (result != null)
{
System.out.println("------redis里面有,我直接返回结果不再打扰mysql: " + result);
return result;
}
//8 redis里面没有,去找msyql查询或叫进行后续业务逻辑
//-------aop精华部分,才去找findUserById方法干活
//userMapper.selectByPrimaryKey(id);
result = joinPoint.proceed();//主业务逻辑查询mysql,放行放行放行
//9 mysql步骤结束,还需要把结果存入redis一次,缓存补偿
if (result != null)
{
System.out.println("------redis里面无,还需要把结果存入redis一次,缓存补偿: " + result);
redisTemplate.opsForValue().set(key, result);
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
测试
/**
* 会将返回值存进redis里,key生成规则需要程序员用SpEL表达式自己指定,value就是程序从mysql查出并返回的user
* redis的key 等于 keyPrefix:matchValue
*/
@Override
@MyRedisCache(keyPrefix = "user",matchValue = "#id")
public User getUserById(Integer id)
{
return userMapper.selectByPrimaryKey(id);
}