使用 Map + 定時器 定時存取微信 Token
阿新 • • 發佈:2019-01-25
微信的 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