背景
spring boot当前开发版本为2.1.2,集成redis使用@Cacheable注解无法设置过期时间,真是一大痛点!也始终想不通,万能的spring为什么没有满足这一点呢?两种解决方案:1.改源码,重新实现SimpleCacheManager;2.放弃@Cacheable,自定义注解。接下来要讲讲怎么实现后者。
实现
1.引入依赖
```javascript
```
2.配置redis
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
import java.io.Serializable;
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheAutoConfiguration {
@Bean
public RedisTemplate
redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory){ RedisTemplate
template = new RedisTemplate (); template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
3.主角-自定义annotaion
key便是redis的主键,当然后续还要解析;type便是方法或接口的出参;expire即是过期时间,一切都是为了它呀
import java.lang.annotation.*;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cach {
String key() default "";
Class type();
//默认缓存时间是一天
long expire() default 60*60*24L;
}
4.将注解使用在方法上
这还没完,主要的解析工作还没做呢!
5.使用AOP拦截打有自定义注解@Cach的方法
import com.alibaba.fastjson.JSON;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class AopCachHandle {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Pointcut(value = "@annotation(com.atm.servicehi.util.Cach)")
public void pointcut(){
}
@Around(value = "pointcut() && @annotation(cach)")
public Object around(ProceedingJoinPoint point, Cach cach){
Method method = getMethod(point);
//根据类名、方法名和参数生成key
final String key = parseKey(cach.key(), method, point.getArgs());
String value = stringRedisTemplate.opsForValue().get(key);
if(null != value){
return JSON.parseObject(value,cach.type());
}
try {
Object proceed = point.proceed();
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(proceed),cach.expire(),TimeUnit.SECONDS);
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
/**
* 获取被拦截方法对象
* MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
* 而缓存的注解在实现类的方法上
* 所以应该使用反射获取当前对象的方法对象
*/
private Method getMethod(ProceedingJoinPoint pjp) {
//获取参数的类型
Class[] argTypes = ((MethodSignature) pjp.getSignature()).getParameterTypes();
Method method = null;
try {
method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return method;
}
private String parseKey(String key, Method method, Object[] args) {
if (StringUtils.isEmpty(key)) {
return method.getName();
}
//获得被拦截方法参数列表
LocalVariableTableParameterNameDiscoverer nd = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = nd.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
key = key.replace(parameterNames[i] + "", args[i] + "");
}
return method.getName() + key;
}
}
好了大功告成!
不要忘记配置redis的数据源呦!
spring:
redis:
host: 10.17.1.61 #redis服务器地址
#timeout: 10000 #超时时间
database: 9 #0-15 16个库 默认0
port: 6379
lettuce:
pool:
max-active: 8 #最大连接数
#max-wait: -1 #默认-1 最大连接阻塞等待时间
max-idle: 8 #最大空闲连接 默认8
min-idle: 0 #最小空闲连接