用自定义 Annotation 改良 Aop cache 的实现

我想大家对于AOP的cache的实现都不陌生,老版本的AOP的cache的key一般是用className+methodName+parameters 拼成一个key,在这里paremeters最好都是String的,如果是一个对象型的在拼key时就会出现问题,可能我每次调某个方法时传进来的参数object都是new出来的,可能尽管他们的内容是一样的,但是他们的内存地址是不一样的,如果我们只是在StringBuffer中append一下的话,就有可能出现我调相同的方法,传相同的参数,但是拼出来的key却是不一样的,这就导致cache失效,而且每调一次cache中就会出现一份相同的返回数据,这里我想到了用自定义Annotation来解决这个问题。
通常情况下,我们cache的都是query的数据,一般都返回一个list,list中存放的是某个model.下面我以我的一个实例进行讲解,DB里有一张表Magazine,做的比较多的查询是根据价格跟售出数目,现在有个需求需要把这些DB中的magazine按每次查询的价格跟售出数目进行分组cache,
Magazine class如下

public class Magazine {
private String isbn;

private String title;
@CacheKey(keySequence = 1)
private BigDecimal price;
@CacheKey(keySequence = 2)
private int copySold;

private int version;

private String deleteFlag;

private boolean displayFlag;

}



然后我把service方法就定义成List<Magezine> getMagazineList(Magazine magazine)每当调这个方法时就传入一个magazine里面只有price跟copySold字段有值,
这里我的做法是,自己写一个Annotatioan用来标注分组条件所用的field,这里就是price 跟copySlod.
Annotation如下

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheKey {
int keySequence();
}



这样在我的AOP的interceptor里,当我在拦截了这个方法后在ping cachekey时先取得标注了CacheKey的field然后通过反射把它们的值取出来拼成一个cachekey,这样不管你每次这个magazine是不是新new 出来的,只要条件相同拼出来的key肯定是一样的(例如查询价格是20快的售出400本的书的magazine的话,不管你每次传进来的magazine是不是同一个对象,只要prize=20,copySold=400,拼出来的key始终是一样的都是className+methodName+20+400)就这保证的cache的有效性,也防止了cache中有重复的数据。拦截器的代码如下,主要逻辑在getCacheKey()这个方法中:


public class AnnotationMethodCacheInterceptor implements MethodInterceptor {

private Cache methodCache;

public void setMethodCache(Cache methodCache) {
this.methodCache = methodCache;
}

@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation invocation) throws Throwable {

String targetName = invocation.getThis().getClass().getInterfaces()[0]
.getName();
String methodName = invocation.getMethod().getName();
Object[] arguments = invocation.getArguments();
Class[] cs = new Class[arguments.length];
for (int k = 0; k < arguments.length; k++) {
cs[k] = arguments[k].getClass();
}
if (invocation.getThis().getClass().getInterfaces()[0].isAnnotationPresent(
EntityCache.class)) {
EntityCache entityCache = (EntityCache)invocation.getThis().getClass().getInterfaces()[0].getAnnotation(EntityCache.class);
return getResult(targetName, methodName, arguments, invocation,entityCache.expireTime());
} else {
if (invocation.getMethod().isAnnotationPresent(MethodCache.class)) {
MethodCache methodCache = invocation.getMethod().getAnnotation(MethodCache.class);
return getResult(targetName, methodName, arguments, invocation,methodCache.expireTime());

} else {
return invocation.proceed();
}
}
}

private Object getResult(String targetName, String methodName, Object[] arguments,
MethodInvocation invocation, int expire) throws Throwable {
Object result;

String cacheKey = getCacheKey(targetName, methodName, arguments[0]);
Element element = methodCache.get(cacheKey);
if (element == null) {
synchronized (this) {
element = methodCache.get(cacheKey);
if (element == null) {
result = invocation.proceed();

element = new Element(cacheKey, (Serializable) result);

//annotation没有设expire值则使用ehcache.xml中自定义值
if (expire > 0) {
element.setTimeToIdle(expire);
element.setTimeToLive(expire);
}
methodCache.put(element);
}
}
}

return element.getValue();
}

public String getCacheKey(String targetNM, String methodNM,Object object) {
StringBuilder datakey = new StringBuilder();
dataKey.append(targeNM).append(methodNM);
try {

// 取得 参数对象中的 field
Field fields[] = object.getClass().getDeclaredFields();

// 找到参数对象中作为key的field
for (Field field : fields) {

// 如果此field家了cachekey的annotation
CacheKey sscCacheKey =
field.getAnnotation(CacheKey.class);
if (sscCacheKey != null) {

// 拼出此field的get方法
char[] charArray = field.getName().toCharArray();
charArray[0] = Character.toUpperCase(charArray[0]);
String methodName = "get" + new String(charArray);

// 通过调用此field的get 方法取到它的值
Method method = object.getClass().getMethod(methodName, new Class[0]);
Object keyValue = method.invoke(object, new Object[0]);


if (keyValue != null) {
if (keyValue instanceof java.util.Date) {
SimpleDateFormat sdf =
new SimpleDateFormat("yyyy-MM-dd");
datakey.append(sdf.format((Date)keyValue));
} else {
datakey.append(keyValue);
}
}
}
}

} catch(Exception e) {
e.printStackTrace();
}
return datakey.toString();

}
}



另外自定义Annotation还可以用在控制cache的粒度上,我可以给类或者方法加上自定义的Annotaion来确定是要给这个class 的所有方法cache,还是只是给某个方法cache.
具体的Annotation的source如下:
EntityCache 用来给class标注

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EntityCache {
int expireTime() default 0;
}


MethodCache用来给需要cache的方法标注

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodCache {
int expireTime() default 0;
}

Example Class

@EntityCache
public interface MagazineService {
public void updateMagazine(Magazine magazine);
public void deleteMagazine(Magazine.MagazineId magazineId);

public Magazine findMagazineByPrimaryKey(Magazine.MagazineId magazineId);
public void saveMagazine(Magazine magazine);
public List<Magazine> getResultByNamedQuery(String name, Object... object);

public List<Magazine> getAll(String beanName);
}


对这些Annotation的handle过程,请参考在上面的AnnotationMethodCacheInterceptor 这个拦截器.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值