使用 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

發佈了141 篇原創文章 · 獲贊 851 · 訪問量 185萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章