使用spring的面向切面变成,只需使用注解和切面,就可以实现对数据的缓存;
import lombok.Data;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
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.stereotype.Component;
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicReference;
@Aspect
@Component
public class DBCacheAdvice {
private static Logger logger = Logger.getLogger(DBCacheAdvice.class);
private static ConcurrentHashMap<String, FutureTask<CacheObject<Object>>> pools = new ConcurrentHashMap<>();
//定义切面、拦截规则
@Pointcut("@annotation(DBCache)") //DBCache是自定义的注解
public void getData() {
}
@Around("getData() && @annotation(dbCache)")
public Object doAuthority(JoinPoint point, DBCache dbCache) {//使用dbCache获取注解设定的参数值
MethodSignature signature = (MethodSignature) point.getSignature();
String cacheName = signature.toString();//使用函数签名作为缓存数据的key,对于特定的需求,做不同的调整吧
long duration = dbCache.duration();
CacheObject<Object> cacheObject = null;
try {
cacheObject = getCacheObject(point, cacheName, duration);//主要是初始化缓存对象
//如果缓存数据过期(超过设定有效时间)或者缓存的数据为空(主要是因为缓存的数据被设置成了softReference,有可能被虚拟机回收)
if (this.isValid(cacheObject, duration)) {
this.refreshData(point, cacheObject, duration);//重新获取数据
}
return cacheObject.getData();//返回数据(要求被拦截的方法具有返回值,这个很容易理解)
} catch (ExecutionException | InvocationTargetException | IllegalAccessException | InterruptedException e) {
logger.error(e);
}
return null;
}
private boolean isValid(CacheObject<Object> cacheObject, long duration) {
long curTimestamp = Calendar.getInstance().getTimeInMillis();
long lastTimestamp = cacheObject.getLastTimestamp();
return duration < curTimestamp - lastTimestamp || cacheObject.getData() == null;
}
private void refreshData(JoinPoint point, CacheObject<Object> cacheObject, long duration) throws InvocationTargetException, IllegalAccessException {
AtomicReference<Thread> casLock = cacheObject.getCas();
Thread thread = Thread.currentThread();
while (!casLock.compareAndSet(null, thread)) {//获取自旋锁,非重入锁
logger.info("CacheObject.status.compareAndSet....");
}
try {
if (this.isValid(cacheObject, duration)) {//成功获取锁后,继续判断缓存数据是否有效(有可能其他线程刚刚重新获取了数据)
Object data = getData(point);
cacheObject.setReference(new SoftReference<Object>(data));
long curTimestamp = Calendar.getInstance().getTimeInMillis();
cacheObject.setLastTimestamp(curTimestamp);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw e;
} finally {
casLock.compareAndSet(thread, null);//释放自旋锁
}
}
private Object getData(JoinPoint point) throws IllegalAccessException, InvocationTargetException {
MethodSignature signature = (MethodSignature) point.getSignature();//获取截取的方法签名
Method method = signature.getMethod(); //获取切面的方法
Object[] args = point.getArgs(); //获取参数,参数没有类型,都是Object对象,要判断。
Object obj = point.getTarget();//获取切面对象
try {
Object result = method.invoke(obj, args);//执行真正的获取数据的方法
return result;
} catch (IllegalAccessException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
}
}
private CacheObject<Object> getCacheObject(JoinPoint point, String cacheName, long duration) throws ExecutionException, InterruptedException {
FutureTask<CacheObject<Object>> futureTask = pools.get(cacheName);//获取concurrentHashMap中的FutureTask
if (futureTask != null) {
return futureTask.get();
} else {
CacheCallable callable = new CacheCallable(point);//创建CacheCallable对象
FutureTask<CacheObject<Object>> newTask = new FutureTask<>(callable);//创建任务对象
futureTask = pools.putIfAbsent(cacheName, newTask);//将任务对象放入到concurrentHashMap中,使用putIfAbsent方法,防止多线程访问的情况
if (futureTask == null) {
futureTask = newTask;
futureTask.run();//保证只有一个执行
}
return futureTask.get();
}
}
class CacheCallable implements Callable<CacheObject<Object>> {
private JoinPoint point;
public CacheCallable(JoinPoint point) {
this.point = point;
}
@Override
public CacheObject<Object> call() throws InvocationTargetException, IllegalAccessException {
Object data = getData(point);
CacheObject<Object> cacheObject = new CacheObject();
SoftReference<Object> reference = new SoftReference(data);
cacheObject.setReference(reference);
return cacheObject;
}
}
@Data
class CacheObject<T> {
private long lastTimestamp;
private AtomicReference<Thread> cas = new AtomicReference<>();
private SoftReference<T> reference;
public CacheObject() {
this.lastTimestamp = Calendar.getInstance().getTimeInMillis();
}
public T getData() {
return reference.get();
}
}
被截取的方法,只需要使用注解 DBCache,并设置duration为40s
@Override
@DBCache(duration = 20000*2)
public Map<String, UserProfileItem> findAllUserProfileItem() {
List<UserProfileItem> list = this.userProfileItemDao.findAllUserProfileItem();
Map<String,UserProfileItem> map = new HashMap<>();
for(UserProfileItem item:list){
String itemName = item.getItemName();//.getName();
map.put(itemName,item);
}
return map;
}
注解
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 DBCache {
/**
* 缓存的有效期,超过有效期,需要刷新数据,默认是1分钟
* @return
*/
long duration() default 60000;
}