Redis缓存系列--(五)自定义Redis缓存注解的使用
自定义Redis缓存注解的实现
我们在Spring的框架中,可以使用注解的形式(@EnableCache和@Cacheable)来实现对查询的数据进行Redis的缓存,我们自己其实也可以自定义一个缓存注解来实现redis缓存的功能。
编写自定义缓存注解
首先,我们要自定义一个Redis缓存注解,之后要将该注解标注到对应的方法上去,代码如下:
/**
* 对方法启用Redis缓存,使其具有方法前判断缓存,方法后如果没有缓存则存储Redis缓存
* 缓存key的格式为:value::key,具体的数值由用户传递的注解参数来提供
* @author young
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRedisCacheable {
/**
* Redis缓存key的前缀
* @return
*/
String value() default "";
/**
* Redis缓存key的后缀
* @return
*/
String key() default "";
}
编写具体AOP代理方法类
注解只是用来标注当前的方法要用来做什么,同时传递相应操作所需要的参数信息。具体要在标注的方法前后实现什么功能,还需要通过Spring AOP的面向切面编程来实现对方法前后所需要进行的处理。具体代码如下:
/**
* MyRedisCacheable注解的具体处理类,实现对标注方法执行前做Redis缓存检查处理,方法执行后执行缓存Redis数据的处理
* @author young
*/
@Component
@Aspect
public class CacheAspect {
private Logger logger = LoggerFactory.getLogger(CacheAspect.class);
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置注解的切入点,即指定切入的方法为
*/
@Pointcut("@annotation(com.young.redis.aop.MyRedisCacheable)")
public void cachePointcut(){
}
/**
* 定义需要进行AOP切面编程的切点是哪个,也即标注MyRedisCacheable注释的方法
* @param proceedingJoinPoint 当前切入方法的上下文
* @return
*/
@Around("cachePointcut()")
public Object doRedisCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object value = null;
try {
//获取切入方法的标签
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
//通过该标签来获取修饰的方法和方法参数
Method method = proceedingJoinPoint.getTarget().getClass().
getMethod(signature.getName(),signature.getMethod().getParameterTypes());
//获取当前方法的注解以及注解的参数
MyRedisCacheable myRedisCacheable = method.getAnnotation(MyRedisCacheable.class);
//redis key的EL表达式
String keyEL = myRedisCacheable.key();
//redis key的前缀
String prifix = myRedisCacheable.value();
//解析EL表达式
ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression =expressionParser.parseExpression(keyEL);
//用来组合EL表达式和方法参数的EL上下文对象
EvaluationContext evaluationContext = new StandardEvaluationContext();
//获取方法的参数,将参数放入evaluationContext
Object[] args = proceedingJoinPoint.getArgs();
DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = defaultParameterNameDiscoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
evaluationContext.setVariable(parameterNames[i],args[i].toString());
}
//使用Expression和EvaluationContext来解析EL表达式
String cacheKey = prifix + "::" + expression.getValue(evaluationContext).toString();
//判断缓存是否存在
value = redisTemplate.opsForValue().get(cacheKey);
if(value != null){
logger.info("从缓存中读取到的key:" + cacheKey +",value:" + value);
return value;
}
//不存在则执行数据库方法进行查询
value = proceedingJoinPoint.proceed();
//将查询到的内容存到Redis
redisTemplate.opsForValue().set(cacheKey,value);
}catch (Throwable throwable){
throwable.printStackTrace();
}
//方法执行后返回查询结果
return value;
}
}
编写配置类
无论是在SpringMvc框架还是Springboot框架中,都可以通过编写配置类的方式来配置需要加载的bean对象,具体的配置类代码如下:
/**
* 通过注解的形式来配置spring中的bean
* 使用自定义注解方式来实现Redis缓存
* @author young
*/
@Configuration
@EnableAspectJAutoProxy //开启AOP自动代理
public class AppConfig {
/**
* 通过注释来获取properties配置文件中的自定义值,引号里边为EL表达式
*/
@Value("${spring.redis.host}")
String host;
@Value("${spring.redis.port}")
int port;
/**
* 通过RedisConnectionFactory来获取RedisTemplate,从而进行Redis的相关操作(注解方式同样需要)
* @return
*/
@Bean
public RedisConnectionFactory redisConnectionFactory(){
//配置Redis的主机和端口
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
//
RedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
return redisConnectionFactory;
}
/**
* 加载RedisTemplate bean
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//指定Redis序列化的方式
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
return redisTemplate;
}
}
这样,一个自定义Redis缓存注解的实现也就基本完成了,这对于我们了解注解的底层原理也是很有帮助的。