MyCacheable.java
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyCacheable {
/**
* 缓存分组名称
*
* @return
*/
String value() default "";
/**
* 缓存 key 值
*
* @return
*/
String key() default "";
}
value:与 Cacheable 的含义一致,只是我们的只支持字符,不支持数组;
key: 与 Cacheable的含义一样,是我们存取绑在的key;
这样我们简单的MyCacheable注解就定义完了。
MyCacheableAnnotationInterceptor.java
import lombok.Getter;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodClassKey;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 1. 在执行方法前,将 @MyCacheable 注解入栈
* 2. 在执行方法后,将 @MyCacheable 注解出栈
*
* @author yudao
*/
public class MyCacheableAnnotationInterceptor implements MethodInterceptor {
@Autowired
RedisTemplate redisTemplate;
/**
* MyCacheable 空对象
*/
static final MyCacheable MY_CACHEABLE_NULL = MyCacheableAnnotationInterceptor.class.getAnnotation(MyCacheable.class);
@Getter
private final Map<MethodClassKey, MyCacheable> MyCacheableCache = new ConcurrentHashMap<>();
public MyCacheableAnnotationInterceptor(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Logger log = LoggerFactory.getLogger(MyCacheableAnnotationInterceptor.class);
log.debug("MyCacheableAnnotationInterceptor 拦截器:" + methodInvocation.getMethod().getName());
// 获取方法上注解
MyCacheable myCacheable = this.findAnnotation(methodInvocation);
//如果没有MyCacheable注解,则执行方法体
if (myCacheable == null) {
// 执行逻辑
return methodInvocation.proceed();
}
//获取到MyCacheable注解了,先试着从缓存中获取值;
String cacheName = myCacheable.value();
// 我们示例就没有Spring的强大能支持SpringEL表达式,
// 我们的只能是没有任何解析,直接拿来就用了。
String key = myCacheable.key();
Object result = redisTemplate.opsForHash().get(cacheName, key);
// 没获取到结果,则执行方法,
if (result == null) {
result = methodInvocation.proceed();
// 空值是否缓存,看自身业务了。
if (result != null) {
redisTemplate.opsForHash().put(cacheName, key, result);
}
}
return result;
}
private MyCacheable findAnnotation(MethodInvocation methodInvocation) {
// 1. 从缓存中获取
Method method = methodInvocation.getMethod();
Object targetObject = methodInvocation.getThis();
Class<?> clazz = targetObject != null ? targetObject.getClass() : method.getDeclaringClass();
MethodClassKey methodClassKey = new MethodClassKey(method, clazz);
MyCacheable myCacheable = MyCacheableCache.get(methodClassKey);
if (myCacheable != null) {
return myCacheable != MY_CACHEABLE_NULL ? myCacheable : null;
}
// 2.1 从方法中获取
myCacheable = AnnotationUtils.findAnnotation(method, MyCacheable.class);
// 2.2 从类上获取
if (myCacheable == null) {
myCacheable = AnnotationUtils.findAnnotation(clazz, MyCacheable.class);
}
// 2.3 添加到缓存中
MyCacheableCache.put(methodClassKey, myCacheable != null ? myCacheable : MY_CACHEABLE_NULL);
return myCacheable;
}
}
项目配置了这个拦截器后,所有添加了@MyCacheable的方法类类在执行方法前都会进入该拦截器,在该方法中解析方法上是否存在MyCacheable注解,若有则先从缓存获取一波结果,能获取到直接返回,获取不到在执行查询方法,获取到后存入缓存。
之后,我们来把拦截器装配起来。
MyCacheableAnnotationAdvisor.java
/**
* {@link MyCacheable} 注解的 Advisor 实现类
*/
@Getter
@EqualsAndHashCode(callSuper = true)
public class MyCacheableAnnotationAdvisor extends AbstractPointcutAdvisor {
private final Advice advice;
private final Pointcut pointcut;
public MyCacheableAnnotationAdvisor(MyCacheableAnnotationInterceptor interceptor) {
this.advice = interceptor;
this.pointcut = this.buildPointcut();
}
protected Pointcut buildPointcut() {
Pointcut classPointcut = new AnnotationMatchingPointcut(MyCacheable.class, true);
Pointcut methodPointcut = new AnnotationMatchingPointcut(null, MyCacheable.class, true);
return new ComposablePointcut(classPointcut).union(methodPointcut);
}
}
MyCacheableConfig.java
import com.luo.chengrui.labs.lab03.mycache.MyCacheableAnnotationAdvisor;
import com.luo.chengrui.labs.lab03.mycache.MyCacheableAnnotationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.data.redis.core.RedisTemplate;
/**
* redis配置
*/
@Configuration
public class MyCacheableConfig {
@Bean
@Role(2)
MyCacheableAnnotationAdvisor myCacheableAnnotationAdvisor(RedisTemplate redisTemplate) {
return new MyCacheableAnnotationAdvisor(new MyCacheableAnnotationInterceptor(redisTemplate));
}
}
/**
* @return
*/
@MyCacheable(value = "USER_INFO", key = "01")
public Object getUserInfo3() {
System.out.println("未获取到缓存,从数据库获取.............");
Map<String, Object> user = new HashMap<>();
user.put("username", "01");
user.put("usercode", "zhangsan");
user.put("sex", "男");
user.put("createtime", DateUtils.format(new Date()));
return user;
}
@Test
public void testMyCacheable() throws InterruptedException {
System.out.println(userService.getUserInfo3());
System.out.println(userService.getUserInfo3());
}
运行结果:
存储效果:
综上,Spring的Cacheable注解的基本逻辑也就是如此了,只是它提供了其他强大的Key解析功能,sync线程中步功能和condition条件缓存功能。