使用标注对于缓存设计的改进
使用标注对于缓存设计的改进
在上一篇“交易系统中WebService服务器端缓存的设计”中,有一个潜在的问题,请看下面代码:
if(mothodName.contains("add") || mothodName.contains("update") || mothodName.contains("delete") ){
// 写方法来了,这意味着数据变更了,缓存可能不可靠,为安全起见需要重新来过
..
}
else{
// 来的是读方法
.
}
// 写方法来了,这意味着数据变更了,缓存可能不可靠,为安全起见需要重新来过
..}
else{
// 来的是读方法
.}
上面的判断里,对写方法的判断作成了硬编码的形式,这对系统可不是件好事,我们可以用Java1.5中新生的Annotation把它消除掉。
首先,我们可以制作一个自己的标注:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于标识方法对缓存影响的标注,如果isRead=true表示这个方法是读方法,需要缓存;否则是写方法,需要清空缓存
* 说明:
*
* 创建时间:2010-2-22 上午08:54:54
* 修改时间:2010-2-22 上午08:54:54
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadWriteAnnotation{
boolean isRead();
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于标识方法对缓存影响的标注,如果isRead=true表示这个方法是读方法,需要缓存;否则是写方法,需要清空缓存
* 说明:
*
* 创建时间:2010-2-22 上午08:54:54
* 修改时间:2010-2-22 上午08:54:54
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadWriteAnnotation{
boolean isRead();
}
这个标注可以用来注释方法,在程序运行期有效,我们可以在标注方法时给isRead方法附上true和false两个值,这样,具体方法是读方法(查询数据库中的一条或者多条记录)还是写方法(更新数据库的一条或者多条记录)就能通过它标识出来了。
下面是做出了标识的接口ITmpService:
import com.dalpha.service.anno.ReadWriteAnnotation;
/**
* TmpService实现类之接口
*
* 创建时间:2010-2-19 下午10:04:34
* 修改时间:2010-2-19 下午10:04:34
*/
public interface ITmpService{
/**
* 解析参数数组,组合成一个领域对象,然后添加到数据库(写方法)
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=false)
public String add(String[] args) throws Exception;
/**
* 解析参数数组,更新领域对象的一个或多个属性,然后更新数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=false)
public String update(String[] args)throws Exception;
/**
* 解析参数数组得到要删除的领域对象的Id,然后根据它删除数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=false)
public String delete(String[] args) throws Exception;
/**
* 解析参数数组得到要取得的领域对象的Id,然后根据它渠道数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=true)
public String getById(String[] args) throws Exception;
/**
* 按条件进行分页查询
* 注意这里的条件最好写全,最好根据数组内容走不同的分支,不要写各种各样的查询函数,这样不方便缓存的处理
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=true)
public String pagedSearchBy(String[] args) throws Exception;
/**
* 按条件进行查询,除了不分页要求和上面函数(pagedSearchBy)一致
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=true)
public String search(String[] args) throws Exception;
/**
* 按ID取得信息
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=true)
public String getInfoById(String[] args) throws Exception;
}
/**
* TmpService实现类之接口
*
* 创建时间:2010-2-19 下午10:04:34
* 修改时间:2010-2-19 下午10:04:34
*/
public interface ITmpService{
/**
* 解析参数数组,组合成一个领域对象,然后添加到数据库(写方法)
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=false)
public String add(String[] args) throws Exception;
/**
* 解析参数数组,更新领域对象的一个或多个属性,然后更新数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=false)
public String update(String[] args)throws Exception;
/**
* 解析参数数组得到要删除的领域对象的Id,然后根据它删除数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=false)
public String delete(String[] args) throws Exception;
/**
* 解析参数数组得到要取得的领域对象的Id,然后根据它渠道数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=true)
public String getById(String[] args) throws Exception;
/**
* 按条件进行分页查询
* 注意这里的条件最好写全,最好根据数组内容走不同的分支,不要写各种各样的查询函数,这样不方便缓存的处理
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=true)
public String pagedSearchBy(String[] args) throws Exception;
/**
* 按条件进行查询,除了不分页要求和上面函数(pagedSearchBy)一致
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=true)
public String search(String[] args) throws Exception;
/**
* 按ID取得信息
*
* @param args
* @return
* @throws Exception
*/
@ReadWriteAnnotation(isRead=true)
public String getInfoById(String[] args) throws Exception;
}
对于实现类我们是不需要标识的,这也是使用接口编程的好处之一。
剩下来,我们在拦截器中取出标识并判断即可,请看下面的代码的粗体部分:
/**
* Service方法的拦截器
* 此拦截器用作缓存使用,每个Service配置一个。
*
* 创建时间:2010-2-19 下午10:42:42
*
* 修改时间:2010-2-19 下午10:42:42
*/
public class ServiceMethodInterceptor implements MethodInterceptor{
// 日志记录器
private static Logger logger = Logger.getLogger(ServiceMethodInterceptor.class);
// 作为缓存的哈希表
private Map<String,Object> cacheMap=new Hashtable<String,Object>();
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String className=invocation.getClass().getName();
String mothodName=invocation.getMethod().getName();
logger.info("类'"+className+"'的方法'"+mothodName+"'将得到调用!");
// 取得在方法上的标注
ReadWriteAnnotation anno=invocation.getMethod().getAnnotation(ReadWriteAnnotation.class);
if(anno.isRead()==false){
// 写方法来了,这意味着数据变更了,缓存可能不可靠,为安全起见需要重新来过
cacheMap.clear();
Object result=invocation.proceed();
logger.info("类'"+className+"'的方法'"+mothodName+"'调用完毕!");
return result;
}
else{
// 来的是读方法
// 通过组合方法名和参数来得到key
StringBuffer sb=new StringBuffer();
sb.append(mothodName+";");
Object[] arr=invocation.getArguments();
String[] arr2=(String[])arr[0];// 这一步的转化是很重要的
for(Object obj:arr2){
sb.append(obj+",");
}
String key=sb.toString();
// 拿Key查看缓存中是否有内容,有则直接返回即可
if(cacheMap.containsKey(key)){
logger.info("直接得到缓存中的结果!");
return cacheMap.get(key);
}
else{
Object result=invocation.proceed();
cacheMap.put(key, result);
logger.info("类'"+className+"'的方法'"+mothodName+"'调用完毕!");
return result;
}
}
}
}
* Service方法的拦截器
* 此拦截器用作缓存使用,每个Service配置一个。
*
* 创建时间:2010-2-19 下午10:42:42
*
* 修改时间:2010-2-19 下午10:42:42
*/
public class ServiceMethodInterceptor implements MethodInterceptor{
// 日志记录器
private static Logger logger = Logger.getLogger(ServiceMethodInterceptor.class);
// 作为缓存的哈希表
private Map<String,Object> cacheMap=new Hashtable<String,Object>();
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String className=invocation.getClass().getName();
String mothodName=invocation.getMethod().getName();
logger.info("类'"+className+"'的方法'"+mothodName+"'将得到调用!");
// 取得在方法上的标注
ReadWriteAnnotation anno=invocation.getMethod().getAnnotation(ReadWriteAnnotation.class);
if(anno.isRead()==false){
// 写方法来了,这意味着数据变更了,缓存可能不可靠,为安全起见需要重新来过
cacheMap.clear();
Object result=invocation.proceed();
logger.info("类'"+className+"'的方法'"+mothodName+"'调用完毕!");
return result;
}
else{
// 来的是读方法
// 通过组合方法名和参数来得到key
StringBuffer sb=new StringBuffer();
sb.append(mothodName+";");
Object[] arr=invocation.getArguments();
String[] arr2=(String[])arr[0];// 这一步的转化是很重要的
for(Object obj:arr2){
sb.append(obj+",");
}
String key=sb.toString();
// 拿Key查看缓存中是否有内容,有则直接返回即可
if(cacheMap.containsKey(key)){
logger.info("直接得到缓存中的结果!");
return cacheMap.get(key);
}
else{
Object result=invocation.proceed();
cacheMap.put(key, result);
logger.info("类'"+className+"'的方法'"+mothodName+"'调用完毕!");
return result;
}
}
}
}
这样,判断方法是读还是写的硬编码就消除了。虽然说花费了一些工夫,但“勿以恶小而为之,勿以善小而不为”,只要是对系统有利的事,我们还是要去做的,只要对系统不利的事,我们必须多家防范,小心杜绝。

浙公网安备 33010602011771号