利用Spring AOP 更新memcached 缓存策略的实现(二)

原创文章,请尊重作者的辛勤劳动,转载请注明!

承接上文,本人终于履行承诺,实现了不使用数据库实现更新缓存的方法,上文链接:利用Spring AOP 更新memcached 缓存策略的实现(一)

实现思路:

1. 执行业务逻辑查询时,第一次在memcached中不存在,则将查询结果序列化后存入memcached中(key:业务方法名+参数类型+参数值+版本号 转 MD5),并且存入当前业务包的版本号(key:业务包名,value:版本号)

2. 执行业务逻辑查询时,检索memcached中已存在key,反序列化后返回Sevice

3. 修改操作时,修改业务逻辑之后,对应业务的包名版本号自增长,查询时重新存入memcached,原key值慢慢的慢慢的就等着死亡吧~~

具体代码:

1. 配置spring aop 查看上文

2. 下载memcached的jar包,相关方法请参考本人之前的博客:这里

3. 编写两个注解,分别为@Cache和@CacheUpdate

package com.dsideal.common;

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

/**
 * 用于查找的时候,放置缓存信息
 * @author Administrator
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Cache {
    //key的前缀
    String prefix();
    //缓存有效期 1000*60*60*2=2小时
    long expiration() default 1000*60*60*2;
}
package com.dsideal.common;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/**
 * 修改时标注
 * @author 周枫
 *
 */
public @interface CacheUpdate {
    //key的前缀
    String prefix();
}

4. memcached的配置类为

package com.dsideal.sys.memcached;

import java.util.Date;

import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;

public class MemcacheBlog {
    // 创建全局的唯一实例


    static MemCachedClient memCachedClient=null;

    // 设置与缓存服务器的连接池
    static{  
        //memcached服务器端IP和端口
        String[] servers = {"192.168.100.102:11211"};    
        SockIOPool pool = SockIOPool.getInstance();    
        pool.setServers(servers);    
        pool.setFailover(true);    
        // 设置初始连接数、最小和最大连接数以及最大处理时间   
        /*    pool.setInitConn(5); 
        pool.setMinConn(5); 
        pool.setMaxConn(250); 
        pool.setMaxIdle(1000 * 60 * 60 * 6); */  
        pool.setInitConn(10);    
        pool.setMinConn(5);    
        pool.setMaxConn(250);    
        pool.setMaintSleep(30);  // 设置主线程的睡眠时间   
        // 设置TCP的参数,连接超时等   
        pool.setNagle(false);    
        pool.setSocketTO(3000);    
        pool.setAliveCheck(true);   

        pool.initialize();    

        memCachedClient = new MemCachedClient();      
        memCachedClient.setPrimitiveAsString(true); 
    }  

    /**
     * <p>功能:
     * get:获取数据的方法
     * get_multi:一次取得多条数据;getmulti可以非同步地同时取得多个键值, 其速度要比循环调用get快数十倍
     * </p>
     * @author 周枫
     * @date 2013-4-3
     * @param 
     * @return Object
     */
    public static Object get(String key)  
    {  
        return memCachedClient.get(key);  
    }  
    /**
     * <p>功能:
     * add:仅当存储空间中不存在键相同的数据时才保存
     * replace:仅当存储空间中存在键相同的数据时才保存
     * set:与add和replace不同,无论何时都保存</p>
     * @author 周枫
     * @date 2013-4-3
     * @param 
     * @return Object
     */
    public static boolean set(String key,Object o)  
    {  
        return memCachedClient.set(key, o);       
    }  
    public static boolean set(String key,Object o,Date ExpireTime)  
    {         
        return memCachedClient.set(key, o, ExpireTime);  
    }  
    public static boolean exists(String key)  
    {  
        return memCachedClient.keyExists(key);  
    }  
    public static boolean delete(String key)  
    {  
        return memCachedClient.delete(key);  
    }  
    public static boolean replace(String key,Object o)  
    {  
        return memCachedClient.replace(key, o);  
    }  

}

5. spring-aop实现类

package com.dsideal.sys.memcached;

import java.lang.reflect.Method;  
import java.util.ArrayList;
import java.util.Date;  
import java.util.List;  

import javax.annotation.Resource;  

import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.Signature;  
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 com.alibaba.fastjson.JSON;

import com.dsideal.common.Cache;
import com.dsideal.common.CacheUpdate;
import com.dsideal.common.Flush;
import com.dsideal.sys.bean.CacheLog;
import com.dsideal.sys.bean.SysLoginPersonBean;
import com.dsideal.sys.service.ICacheLogService;
import com.dsideal.common.MD5;

@Aspect  
@Component 
public class CacheAopNoMysql {

    //  @Pointcut("execution(* add*(..)) || (execution(* del*(..))) || (execution(* get*(..)))")  
    //* com.dsideal.sys.service.impl.*.getMemcache*(..)
    @Pointcut("execution (* com.dsideal.sys.service.impl.*.memcache*(..))")  
    public void pointcut(){}  

    //方法执行前调用  
    //@Before("pointcut()")  
    public void before() {  
        System.out.println("before");  //2
    }  

    @Resource
    private ICacheLogService cacheLogService;  



    //方法执行的前后调用   
    @Around("pointcut()")  
    //ProceedingJoinPoint 目标类连接点对象
    public Object doAround(ProceedingJoinPoint call) throws Throwable{  
        //返回最终结果
        Object result = null;  
        //定义版本号,默认为1
        String prefixValue = "1";
        Method[] methods = call.getTarget().getClass().getDeclaredMethods();    
        Signature signature = call.getSignature();  
        MethodSignature methodSignature = (MethodSignature) signature;    
        Method method = methodSignature.getMethod();  
        for(Method m:methods){
            //循环方法,找匹配的方法进行执行  
            if(m.getName().equals(method.getName())){
                //增加
                if(m.isAnnotationPresent(Cache.class)){  
                    Cache cache = m.getAnnotation(Cache.class);
                    Object tempType = m.getGenericReturnType();
                    //System.out.println(m.get);
                    //如果memcached中存在
                    if(cache!=null){  
                        //获取注解前缀,这里为 sys,实际使用是为各个业务包名
                        String prefix = cache.prefix();  
                        //获取版本号
                        if(null != MemcacheBlog.get(prefix)){
                            prefixValue = MemcacheBlog.get(prefix).toString();
                        }
                        //获取方法名+参数类型+参数值+版本号 转 MD5
                        String tempKey = this.getKey(method, call.getArgs(), prefixValue);  
                        //存入memcached的最终key值
                        String key = prefix+"_"+tempKey;  
                        result =MemcacheBlog.get(key);  
                        if(null == result){  
                            try {  
                                //执行aop拦截的方法
                                result = call.proceed();  
                                //获取注解配置memcached死亡时间
                                long expiration = cache.expiration();
                                //1000*60*60*2==2小时过期  
                                Date d=new Date();  
                                //memcached死亡时间
                                d=new Date(d.getTime()+expiration);
                                //利用fastjson序列化list<bean>存入memcached中
                                //具体fastjson使用方法请参考:http://www.cnblogs.com/cczhoufeng/archive/2013/04/03/2997871.html
                                if(prefixValue.equals("1")){
                                    MemcacheBlog.set(prefix, prefixValue);
                                } 
                                MemcacheBlog.set(key, JSON.toJSONString(result), d);                                
                            } catch (Throwable e) {  
                                e.printStackTrace();  
                            }  
                        }  else {
                            //如果memcached中存在结果,需要将result反序列化后返回结果
                            String memresult = result.toString();
                            //反序列化
                            List<SysLoginPersonBean> list = JSON.parseArray(memresult, SysLoginPersonBean.class);
                            result = list;
                            //这里是利用fastjson反序列化输出的方法
                            //String memresult = result.toString();
                            //List<SysLoginPersonBean> list =  JSON.parseArray(memresult, SysLoginPersonBean.class);
                            //for (int i = 0; i < list.size(); i++) {
                            //    System.out.println(list.get(i).getReal_name());
                            //}
                        }

                    }  
                } else  if(m.isAnnotationPresent(CacheUpdate.class)){  
                    //如果修改操作时
                    CacheUpdate cUpdate = m.getAnnotation(CacheUpdate.class);  
                    if(cUpdate!=null){  
                        result = call.proceed();
                        String prefix = cUpdate.prefix();  
                        //获取当前版本号
                        if(null != MemcacheBlog.get(prefix)){
                            prefixValue = MemcacheBlog.get(prefix).toString();
                        }
                        //修改后,版本号+1
                        MemcacheBlog.replace(prefix, Integer.parseInt(prefixValue.toString()) + 1);
                        System.out.println(MemcacheBlog.get(prefix).toString());
                    }  
                }else{  
                    try {  
                        result = call.proceed();  
                    } catch (Throwable e) {  
                        e.printStackTrace();  
                    }  
                }  
                break;  
            }  
        }

        return result;
    }

    /** 
     * 组装key值 
     * @param method 
     * @param args 
     * @return 
     */  
    private String getKey(Method method, Object [] args, String prefixValue){  
        StringBuffer sb = new StringBuffer();   
        //获取方法名
        String methodName = method.getName();
        //获取参数类型
        Object[] classTemps = method.getParameterTypes();
        //存入方法名
        sb.append(methodName);

        for (int i = 0; i < args.length; i++) {
            sb.append(classTemps[i]+"&");
            if (null == args[i]) {
                sb.append("null");
            } else if ("".equals(args[i])) {
                sb.append("*");
            } else {
                sb.append(args[i]);
            }
        }
        sb.append(prefixValue);
        return MD5.getMD5(sb.toString());  

    }  
}

6. MD5的实现类,见上文

7. 我做列子用的业务表,mysql数据库,见上文

8. 实体bean,见上文

9. 接口(这里我偷懒了,和上文使用的是同一个接口文件,用不到的自己过滤下吧,呵呵)

package com.dsideal.sys.service;

import java.util.List;

import com.dsideal.sys.bean.CacheLog;
import com.dsideal.sys.bean.SysLoginPersonBean;

public interface ICacheLogService {

    /**
     * <p>功能:增加memcached数据文件到临时表中</p>
     * @author 周枫
     * @date 2013-4-9
     * @param 
     * @return void
     */
    public void add(CacheLog log);

    /**
     * <p>功能:查询以prefix为前缀的所有key值,在更新删除时使用此方法</p>
     * @author 周枫
     * @date 2013-4-9
     * @param 
     * @return List<CacheLog>
     */
    public List<CacheLog> findListByPrefix(String prefix);

    /**
     * <p>功能:删除操作时,aop拦截</p>
     * @author 周枫
     * @date 2013-4-9
     * @param 
     * @return void
     */
    public void memcacheDeleteByPrefix();

    /**
     * <p>功能:删除临时表记录的数据</p>
     * @author 周枫
     * @date 2013-4-9
     * @param 
     * @return int
     */
    public int deleteByPrefix(String prefix);

    /**
     * <p>功能:查找例子,查找所有人员数据,后面的person_id没有使用,只是为了测试key值的生成策略</p>
     * @author 周枫
     * @date 2013-4-9
     * @param 
     * @return List<SysLoginPersonBean>
     */
    public List<SysLoginPersonBean> memcacheFindAll(int b_use,String person_id);

    /**
     * <p>功能:测试方法,可以忽略</p>
     * @author 周枫
     * @date 2013-4-9
     * @param 
     * @return List<CacheLog>
     */
    public List<CacheLog> memcacheCacheLogFindAll();

    /**
     * <p>功能:修改人员</p>
     * @author 周枫
     * @date 2013-4-9
     * @param 
     * @return int
     */
    public int memcacheupdateSysLoginPersonBean(String prefix,String person_id,String real_name);
    
    /**
     * <p>功能:不用数据库临时表更新缓存时修改人员</p>
     * @author 周枫
     * @date 2013-4-9
     * @param 
     * @return int
     */
    public int memcacheUpdateNoSql(String prefix,String person_id,String real_name);


}

10. 接口的实现类,"sys"为业务包名,为key的前缀,expiration:自定义memcached死亡时间,版本号无死亡时间

package com.dsideal.sys.service.impl;

import java.util.List;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.dsideal.common.Cache;
import com.dsideal.common.CacheUpdate;
import com.dsideal.common.Flush;
import com.dsideal.sys.bean.CacheLog;
import com.dsideal.sys.bean.SysLoginPersonBean;
import com.dsideal.sys.dao.CacheLogDao;
import com.dsideal.sys.service.ICacheLogService;

@Service
public class CacheLogServiceImpl implements ICacheLogService {

    @Resource
    private CacheLogDao dao;

    @Override
    public void add(CacheLog log) {
        dao.add(log);
    }

    @Override
    public List<CacheLog> findListByPrefix(String prefix) {
        // TODO Auto-generated method stub
        return dao.findListByPrefix(prefix);
    }

    @Override
    @Flush(prefix="sys")
    public void memcacheDeleteByPrefix() {
        // TODO Auto-generated method stub
    }

    @Override
    public int deleteByPrefix(String prefix) {
        // TODO Auto-generated method stub
        return dao.deleteByPrefix(prefix);
    }

    @Override
    @Cache(prefix="sys",expiration=1000*60*60*2)
    public List<SysLoginPersonBean> memcacheFindAll(int b_use,String person_id) {
        // TODO Auto-generated method stub
        return dao.findAll(b_use);
    }

    @Override
    @Cache(prefix="sys",expiration=1000*60*60*2)
    public List<CacheLog> memcacheCacheLogFindAll() {
        // TODO Auto-generated method stub
        return dao.findCacheLogAll();
    }

    @Override
    @Flush(prefix="sys")
    public int memcacheupdateSysLoginPersonBean(String prefix,String person_id,String real_name) {
        return dao.updateSysLoginPersonBean(person_id,real_name);

    }
    
    @Override
    @CacheUpdate(prefix="sys")
    public int memcacheUpdateNoSql(String prefix,String person_id,String real_name) {
        return dao.updateSysLoginPersonBean(person_id,real_name);

    }

}

11. dao层

package com.dsideal.sys.dao;

import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;

import java.util.Date;
import java.util.List;

import com.dsideal.common.BaseDao;
import com.dsideal.common.Utils.RSMapper;
import com.dsideal.sys.bean.CacheLog;
import com.dsideal.sys.bean.SysLoginPersonBean;


@Repository
public class CacheLogDao extends BaseDao {
    public void add(CacheLog log) {
        try {
            String sql = "INSERT INTO t_cache_log(prefix,cache_key,add_time) VALUES (?,?,?)";
            int result = 0;
            Date now = new Date();
            result = this.jdbcTemplate.update(sql, log.getPrefix(),log.getCache_key(),now);
            System.out.println("增加成功");
        } catch (DataAccessException e) {
            e.printStackTrace();
            System.out.println("增加报错");
        }
    }

    public int deleteByPrefix(String prefix) {
        try {
            String sql = "DELETE FROM t_cache_log WHERE prefix = ?";
            int result = 0;
            result = this.jdbcTemplate.update(sql, prefix);
            return result;
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return 0;
    }

    public List<CacheLog> findListByPrefix(String prefix) {
        try {
            String sql = "SELECT * FROM t_cache_log WHERE prefix = ?";
            return RSMapper.queryList(jdbcTemplate, sql, CacheLog.class, prefix);
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public List<SysLoginPersonBean> findAll(int b_use) {
        try {
            String sql = "SELECT * FROM t_sys_loginperson WHERE b_use = ?";
            return RSMapper.queryList(jdbcTemplate, sql, SysLoginPersonBean.class,b_use);
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public List<CacheLog> findCacheLogAll() {
        try {
            String sql = "SELECT * FROM t_cache_log";
            return RSMapper.queryList(jdbcTemplate, sql, CacheLog.class);
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public int updateSysLoginPersonBean(String person_id,String real_name) {
        int result = 0;
        try {
            String sql = "UPDATE t_sys_loginperson SET real_name = ? WHERE person_id = ?";

            return this.jdbcTemplate.update(sql, real_name,person_id);
        } catch (DataAccessException e) {
            e.printStackTrace();
            System.out.println("修改时报错");
        }
        return result;
    }
}

12. Service测试类

package com.dsideal.sys.test;

import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.dsideal.sys.bean.CacheLog;
import com.dsideal.sys.bean.SysLoginPersonBean;
import com.dsideal.sys.service.ICacheLogService;
import com.dsideal.sys.service.impl.CacheLogServiceImpl;

@RunWith(SpringJUnit4ClassRunner.class)
//指定Spring的配置文件 /为classpath下
@ContextConfiguration(locations = {"/spring-mvc.xml"}) 
public class CacheLogServiceTest {

    @Autowired
    private ICacheLogService impl;

    @Before //在每个测试用例方法之前都会执行  
    public void init(){  

    }  

    @After //在每个测试用例执行完之后执行  
    public void destory(){  

    }  

    @Test
    public void add() {
        CacheLog log = new CacheLog();
        impl.add(log);
    }

    @Test
    public void findAll() {
        int b_use = 1;
        String person_id = "";
        List<SysLoginPersonBean> list = impl.memcacheFindAll(b_use,person_id);
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getReal_name());
        }

    }

    @Test
    public void deleteByPrefix() {
        System.out.println("1");
        impl.memcacheDeleteByPrefix();
        System.out.println("删除成功");
    }

    @Test
    public void findCacheLogAll() {
        List<CacheLog> list = impl.memcacheCacheLogFindAll();
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getCache_key());
        }

    }

    @Test
    public void updateToDeleteToFind() {
        String prefix = "sys";
        String person_id1 = "0E04DE60-7264-4FE7-9A6C-5AB843B603CC";
        String person_id2 = "";
        String real_name = "初中历史管理员1";
        int result = impl.memcacheupdateSysLoginPersonBean(prefix, person_id1, real_name);
        if (result > 0) {
            System.out.println("修改成功");
        }

        int b_use = 1;
        List<SysLoginPersonBean> list = impl.memcacheFindAll(b_use,person_id2);
        System.out.println("查询成功");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getReal_name());
        }
    }
    
    @Test
    public void updateNoSqlToDeleteToFind() {
        String prefix = "sys";
        String person_id1 = "0E04DE60-7264-4FE7-9A6C-5AB843B603CC";
        String person_id2 = "";
        String real_name = "初中历史管理员2";
        int result = impl.memcacheUpdateNoSql(prefix, person_id1, real_name);
        if (result > 0) {
            System.out.println("修改成功");
        }
        int b_use = 1;
        List<SysLoginPersonBean> list = impl.memcacheFindAll(b_use,person_id2);
        System.out.println("查询成功");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i).getReal_name());
        }
    }
}

具体测试方法同上文

PS: 具体测试方法就是以上的步骤了,代码写的有点乱,如果有问题,希望大家可以指出,谢谢~~

posted @ 2013-04-10 09:14  东师理想--周枫  阅读(2414)  评论(5编辑  收藏  举报