使用标注对于缓存设计的改进

使用标注对于缓存设计的改进

在上一篇“交易系统中WebService服务器端缓存的设计”中,有一个潜在的问题,请看下面代码:

        if(mothodName.contains("add"|| mothodName.contains("update"|| mothodName.contains("delete") ){
            
// 写方法来了,这意味着数据变更了,缓存可能不可靠,为安全起见需要重新来过
            ..
        }
        
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();
}


这个标注可以用来注释方法,在程序运行期有效,我们可以在标注方法时给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;
}


对于实现类我们是不需要标识的,这也是使用接口编程的好处之一。

剩下来,我们在拦截器中取出标识并判断即可,请看下面的代码的粗体部分:

/**
 * 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;
            }
        }
    }
}


这样,判断方法是读还是写的硬编码就消除了。虽然说花费了一些工夫,但“勿以恶小而为之,勿以善小而不为”,只要是对系统有利的事,我们还是要去做的,只要对系统不利的事,我们必须多家防范,小心杜绝。

posted @ 2011-10-19 20:28  jex  阅读(155)  评论(0)    收藏  举报