1. 程式人生 > >使用 Map + 定時器 定時存取微信 Token

使用 Map + 定時器 定時存取微信 Token

微信的 Token 可以存放在資料庫裡 , 需要的時候從資料庫讀取 . 但是這樣日積月累 , 資料量會逐漸撐大 , 而且 token 我們用指定時間(兩小時內)就會丟棄 , 以後也不會用到過期的 token , 所以將它儲存在資料庫是很浪費資源的 . 所以我寫了個快取類來定時獲取微信的 token 到 map
首先 , 定義存 map 的方法

package com.newtec.weixin.manager;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import
java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.newtec.weixin.pojo.CacheConfModel; /** * 使用 map 作為一級快取 , 必要時代替資料庫存取token */ public class CacheMgr { private
static Logger log = LoggerFactory.getLogger(CacheMgr.class); public static Map cacheMap = new HashMap();//存放快取 public static Map cacheConfMap = new HashMap();//存放配置資訊 static final Lock lock = new ReentrantLock(); public static String key; private static Object value; private static
CacheConfModel ccm; //單例 private static CacheMgr cacheMgr = null; private CacheMgr(){} public static CacheMgr getIntance(){ if(cacheMgr == null){ cacheMgr = new CacheMgr(); } return cacheMgr; } /** * 增加快取資訊 * @param key * @param value * @param ccm * @return */ public class AddCache implements Runnable{ public AddCache(String key,Object value,CacheConfModel ccm) { CacheMgr.key = key; CacheMgr.value = value; CacheMgr.ccm = ccm; } @Override public void run() { //定時執行緒處理的業務 log.info("開始清理快取--- {}",key); ClearCache.clearCache();//每次執行清除上次快取 只有一個token快取能夠存在 log.info("開始快取--- {}",key); lock.lock(); try { cacheMap.put(key, value); cacheConfMap.put(key, ccm); log.info("增加快取結束:key={},value={}",key,value); log.info("快取大小為={}",cacheMap.size()); } catch (Exception e) { e.printStackTrace(); }finally{ lock.unlock(); } } } /** * 獲取快取實體 * @param key 快取鍵 * @return */ public Object getValue(String key){ Object obj = cacheMap.get(key); if(obj != null){ return obj; }else{ return null; } } /** * 獲取快取總數 * @return 快取總數 */ public int getSize(){ return cacheMap.size(); } /** * 刪除快取 * @param key * @return */ public boolean removeCache(String key){ boolean flag = false; try { cacheMap.remove(key); cacheConfMap.remove(key); flag = true; } catch (Exception e) { e.printStackTrace(); } return flag; } }

如果你沒有 log 的 jar 包 , 可以選擇不列印 log

由於我們每次只用到一個 token , 當最新的 token 生成時 ,以前的 token 都會失效 , 所以在增加 token快取之前需要清理之前的快取

public class ClearCache{
    static final Lock lock = new ReentrantLock();
    final Condition myLock = lock.newCondition();

    public static void clearCache(){
        while(true){
            try {
                lock.lock();//每次只允許一個執行緒清理快取
                Set tempSet = new HashSet();
                Set set = CacheMgr.cacheMap.keySet();
                Iterator it = set.iterator();
                while(it.hasNext()){//迭代
                    String key = (String) it.next();
                    CacheConfModel ccm = (CacheConfModel)CacheMgr.cacheConfMap.get(key);  
                    tempSet.add(key);  //記錄清除資料

                }
                //清除
                Iterator tempIt = tempSet.iterator();
                while(tempIt.hasNext()){
                    String key = (String) tempIt.next();
                    CacheMgr.cacheMap.remove(key);
                    CacheMgr.cacheConfMap.remove(key);
                }
                if(CacheMgr.cacheMap.size()<=0 && CacheMgr.cacheConfMap.size()<=0){//如果快取已被清空,跳出迴圈 , 否則繼續清理 , 知道 map 中沒有 token 為止
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                lock.unlock();
            }
        }
    }

}

然後我們需要一個 servlet 執行快取存取過程

public class AutoLoadCacheServlet extends HttpServlet {

    private static CacheMgr cacheMgr = CacheMgr.getIntance();

    private static ScheduledThreadPoolExecutor stpe =  new ScheduledThreadPoolExecutor(1);

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        CacheConfModel cModel = new CacheConfModel();
        Date d = new Date();
        cModel.setBeginTime(d.getTime());
        cModel.setDurableTime(60);
        cModel.setForever(true);
        AddCache addCache = cacheMgr.new AddCache("TOKEN", CommonUtil.getToken(
                CommonUtil.APPID, CommonUtil.APPSECRET).getAccessToken(), cModel);
        //執行的回撥方法,第一次執行時間,週期,時間型別           
        stpe.scheduleAtFixedRate(addCache, 0, 7000,TimeUnit.SECONDS);                       //System.out.println(CacheMgr.cacheMap.get("TOKEN"));
    }
    }

快取配置類 – CacheConfModel

public class CacheConfModel {
    private long beginTime;// 快取開始時間
    private boolean isForever = false;// 是否持久儲存
    private int durableTime;// 持續時間

    public long getBeginTime() {
        return beginTime;
    }

    public void setBeginTime(long beginTime) {
        this.beginTime = beginTime;
    }

    public boolean isForever() {
        return isForever;
    }

    public void setForever(boolean isForever) {
        this.isForever = isForever;
    }

    public int getDurableTime() {
        return durableTime;
    }

    public void setDurableTime(int durableTime) {
        this.durableTime = durableTime;
    }

}

最後在啟動 web 專案時 , 自動裝載這個 servelet
在 web.xml 配置

<servlet>
        <servlet-name>autoLoadCacheServlet</servlet-name>
        <servlet-class>
com.newtec.weixin.servlet.AutoLoadCacheServlet
        </servlet-class>
        <load-on-startup>0</load-on-startup>
    </servlet>

這樣當 web 專案啟動時 , 快取就會通過定時器自動增加並清理
這裡不使用 timer 的原因是
Timer 和 Timertask 是單執行緒的,而且沒有處理異常,當程式超過指定時間沒有執行的時候,程式就會異常終止 , 即使我們用執行緒鎖使得快取存取/清理同步了 , 仍然不能保證不會在處理快取的時候跑出異常導致程式終止
所以需要多個執行緒或者需要額外的靈活性和功能時,推薦使用ScheduledThreadPoolExecutor