Spring Boot(十四):自定義緩存管理器

1. 介紹

         目前業界使用廣泛的內存數據存儲有redis、Memcached。相比 Memcached,Redis 支持更豐富的數據結構。同時支持數據持久化。除此之外,Redis 還提供一些類數據庫的特性,比如事務,HA,主從庫。可以說 Redis 兼具了緩存系統和數據庫的一些特性,因此有着豐富的應用場景。不過本篇文件的重點不是redis,重點是手寫一個簡易的內存型緩存數據庫。

2. 緩存的基本功能 

        作爲緩存,一些基本的功能:可以存儲數據、獲取數據、刪除數據、數據淘汰策略等等。。。

3. 編碼

       瞭解了基本功能後,下面就開始編碼啦。。。

      3.1 定義基本方法

            緩存的頂級接口。。。   

         Cache.java (interface)          

/**
 * 
 * 緩存頂級接口
 * @author reyco
 *
 */
public interface Cache {
	/**
	 * 添加緩存對象
	 * @param key 		緩存的key,通過該key可以獲取對應的緩存對象
	 * @param obj		緩存的對象...  
	 * @return
	 */
	public Boolean put(String key, Object obj);
	/**
	 * 添加緩存對象
	 * @param key 			緩存的key,通過該key可以獲取對應的緩存對象
	 * @param obj			緩存的對象...  
	 * @param duration		存活時長...
	 * @return
	 */
	public Boolean put(String key, Object obj,Long duration);
	/**
	 * 獲取緩存對象
	 * @param key	緩存的key,通過該key可以獲取對應的緩存對象
	 * @return
	 */
	public Object get(String key);
	/**
	 * 緩存的數量大小
	 * @return
	 */
	public Integer getSize();
	/**
	 * 移除緩存
	 * @param key   緩存的key,通過該key可以獲取對應的緩存對象
	 * @return
	 */
	public Boolean remove(String key);
	/**
	 * 清空緩存
	 * @return
	 */
	public Boolean clear();
	/**
	 * 緩存淘汰策略
	 */
	public void removeStrategy();
}

      3.2 緩存對象數據結構基本信息

                 ExpireInfo.java                

/**
 * 描述緩存對象
 * @author reyco
 *
 */
public class ExpireInfo {
	
	/**
	 * 開始時間
	 */
	private Date startTime;
	/**
	 * 存活時長
	 */
	private Long duration;
	/**
	 * 結束時間毫秒數
	 */
	private Long ExpireTime;

	public Date getStartTime() {
		return startTime;
	}
	public void setStartTime(Date startTime) {
		this.startTime = startTime;
	}
	public Long getDuration() {
		return duration;
	}
	public void setDuration(Long duration) {
		this.duration = duration;
	}
	public Long getExpireTime() {
		return ExpireTime;
	}
	public void setExpireTime(Long expireTime) {
		ExpireTime = expireTime;
	}
}

   

      3.3 基本方法的實現

                ConcurrentHashMapCache.java 實現     

/**
 *	通過靜態內部類實現単例模式 
 * 		這種方法:線程安全,調用效率高,並且實現了延時加載
 * @author reyco
 *
 */
public class ConcurrentHashMapCache implements Cache {
	
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	/**
	 * 緩存對象 key value
	 */
	private final static ConcurrentMap<String, Object> store = new ConcurrentHashMap<>();
	/**
	 * 緩存的信息: 過去時間。。。
	 */
	private final static ConcurrentMap<String, ExpireInfo> storeObj = new ConcurrentHashMap<>();
	/**
	 * 私有構造器
	 */
	private ConcurrentHashMapCache() {}
	
	/**
	 * 私有獲取屬性的靜態內部類
	 * @author reyco
	 *
	 */
	private static class Instance {
		private static final ConcurrentHashMapCache instance = new ConcurrentHashMapCache();
	}
	
	/**
	 * 方法沒有同步,效率高!
	 * 
	 * @return
	 */
	public static ConcurrentHashMapCache getInstance() {
		return Instance.instance;
	}
	/**
	 * 添加元素
	 * 
	 * @param key
	 * @param value
	 * @return
	 */
	@Override
	public Boolean put(String key, Object value) {
		return put(key, value, null);
	}
	/**
	 * 添加元素
	 * @param key
	 *            key
	 * @param value
	 *            value
	 * @param duration
	 *            有效時間/單位秒
	 * @return
	 */
	@Override
	public Boolean put(String key, Object obj, Long duration) {
		if (StringUtils.isBlank(key) || null == obj) {
			return false;
		}
		Long ExpireTime = 0L;
		// 默認緩存不失效時長
		long currentTimeMillis = System.currentTimeMillis();
		if (null == duration || duration == -1) {
			// 默認緩存不失效
			ExpireTime = Long.MAX_VALUE;
		}else {
			ExpireTime = currentTimeMillis + 1000*duration;
		}
		// 構建緩存描述對象
		ExpireInfo expireInfo = new ExpireInfo();
		expireInfo.setStartTime(new Date());
		expireInfo.setDuration(duration);
		expireInfo.setExpireTime(ExpireTime);
		//添加緩存
		store.put(key, obj);
		storeObj.put(key, expireInfo);
		logger.debug("key="+key+",value="+obj+",expireTime="+ExpireTime+",添加到緩存中.");
		return true;
	}
	/**
	 * 獲取元素
	 * @param key
	 * @return
	 */
	@Override
	public Object get(String key) {
		if (StringUtils.isBlank(key)) {
			throw new RuntimeException("key Can not be null");
		}
		// 判斷key是否存在
		if (!storeObj.containsKey(key)) {
			return null;
		}
		// 是否過期
		ExpireInfo expireInfo = storeObj.get(key);
		long nowTime = System.currentTimeMillis();
		Long expireTime = expireInfo.getExpireTime();
		if (nowTime > expireTime) {
			// 過期
			remove(key);
			return null;
		}
		Object object = store.get(key);
		logger.debug("獲取緩存,key="+key+",value="+object);
		return object;
	}
	
	/**
	 * 獲取元素數量
	 * @param key
	 * @return
	 */
	@Override
	public Integer getSize() {
		return store.size();
	}

	/**
	 * 移除元素
	 * @param key
	 * @return
	 */
	@Override
	public Boolean remove(String key) {
		if (StringUtils.isBlank(key)) {
			return false;
		}
		Object value = null;
		if(store.containsKey(key)) {
			value = store.get(key);
			store.remove(key);
			storeObj.remove(key);
		}
		logger.debug("key="+key+",value="+value+",已過期,被移除了.");
		return true;
	}
	/**
	 * 清空元素
	 * @return
	 */
	@Override
	public Boolean clear() {
		store.clear();
		storeObj.clear();
		logger.debug("緩存被清空.");
		return true;
	}
	/**
	 * 緩存刪除策略
	 */
	@Override
	public void removeStrategy() {
		for (Entry<String, ExpireInfo> entry : storeObj.entrySet()) {
			ExpireInfo expireInfo = entry.getValue();
			// 是否過期
			long nowTime = System.currentTimeMillis();
			Long expireTime = expireInfo.getExpireTime();
			if (nowTime > expireTime) {
				String key = entry.getKey();
				Object object = store.get(key);
				// 過期
				remove(key);
			}
		}
	}
}

    3.4 定時刪除過期的緩存數據 

                使用springboot的定時任務比較方法。。。        

@Component
public class CacheTask {
	
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Scheduled(cron = "0 * * * * ?")
	public void clear() {
		logger.debug("緩存定時任務執行開始:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
		ConcurrentHashMapCache instance = ConcurrentHashMapCache.getInstance();
		instance.removeStrategy();
		logger.debug("緩存定時任務執行結束:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
	}
}

         使用原生quartz:          

@Component
public class TaskRemoveStrategy implements Job {
	
	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		ConcurrentHashMapCache instance = ConcurrentHashMapCache.getInstance();
		instance.removeStrategy();
	}
	
	@PostConstruct
	public void task() {
		try {
			Date startTime = new Date();
			startTime.setTime(startTime.getTime()+1000*30);
			// 創建一個JobDetail
			JobDetail taskJobDetail = JobBuilder.newJob(TaskRemoveStrategy.class)
					.withIdentity("TaskDetailJob","TaskDetailGroup")
					.build();
			// Cron表達式:  秒 分 時 日 月 周 年
			CronTrigger taskTrigger = (CronTrigger)TriggerBuilder.newTrigger()
							// 30秒後執行任務
							.startAt(startTime)
							// 每分鐘執行
							.withSchedule(CronScheduleBuilder.cronSchedule("0 * * * * ? *"))
							.build();
			// 創建SchedulerFactory
			SchedulerFactory sf = new StdSchedulerFactory();
			// 創建Scheduler
			Scheduler scheduler = sf.getScheduler();
			scheduler.start();
			scheduler.scheduleJob(taskJobDetail,taskTrigger);
		} catch (SchedulerException e) {
			e.printStackTrace();
		}
	}
	
	
}

     3.4 操作緩存的工具類                

public class CacheUtils {

	private static ConcurrentHashMapCache getConcurrentHashMapCache() {
		return ConcurrentHashMapCache.getInstance();
	}
	/**
	 * 添加元素
	 * @param key
	 *            key
	 * @param value
	 *            value
	 * @param duration
	 *            有效時間/單位秒
	 * @return
	 */
	public static void put(String key,Object value,Long expireTime) {
		ConcurrentHashMapCache concurrentHashMapCache = getConcurrentHashMapCache();
		concurrentHashMapCache.put(key,value,expireTime);
	}
	/**
	 * 添加元素
	 * @param key
	 * @param value
	 * @return
	 */
	public static void put(String key,Object value) {
		ConcurrentHashMapCache concurrentHashMapCache = getConcurrentHashMapCache();
		concurrentHashMapCache.put(key, value);
	}
	/**
	 * 獲取緩存對象
	 * @param key
	 * @return
	 */
	public static Object get(String key) {
		ConcurrentHashMapCache concurrentHashMapCache = getConcurrentHashMapCache();
		return concurrentHashMapCache.get(key);
	}
	/**
	 * 移除緩存對象
	 * @param key
	 * @return
	 */
	public static Object remove(String key) {
		ConcurrentHashMapCache concurrentHashMapCache = getConcurrentHashMapCache();
		return concurrentHashMapCache.remove(key);
	}
	
}

     3.5 測試——模擬session功能

                 LoginController.java        

@RequestMapping("/api")
@RestController
public class LoginController {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@RequestMapping("captcha")
	public Result getCaptcha() {
		Result result = new Result();
		String code = CaptchaUtils.createCode();
		String key = UUID.randomUUID().toString().replace("-", "");
		CacheUtils.put(key, code,60L);
		logger.info("生成驗證碼");
		Captcha captcha = new Captcha(key,code);
		result.setData(captcha);
		result.setFlag(true);
		return result;
	}
	
	@RequestMapping("/login")
	public Result Login(String name,String password,String captcha,HttpServletRequest request,HttpServletResponse response) {
		Result result = new Result();
		if(StringUtils.isBlank(name) || StringUtils.isBlank(password)|| StringUtils.isBlank(captcha) ) {
			result.setData(null);
			result.setFlag(false);
			result.setMsg("參數錯誤");
			logger.info("參數錯誤");
			return result;
		}
		if(!name.equals("admin") || !password.equals("123456")) {
			result.setData(null);
			result.setFlag(false);
			result.setMsg("用戶名或密碼錯誤。。。");
			logger.info("用戶名或密碼錯誤。。。");
			return result;
		}
		String captchaCookie = CookieUtil.getCookieByName(request, "captcha");
		if(StringUtils.isBlank(captchaCookie)) {
			result.setData(null);
			result.setFlag(false);
			result.setMsg("沒有驗證碼");
			logger.info("沒有驗證碼");
			return result;
		}
		Object selCaptcha = CacheUtils.get(captchaCookie);
		CacheUtils.remove(captchaCookie);
		if(null == selCaptcha ) {
			result.setData(null);
			result.setFlag(false);
			result.setMsg("驗證碼失效");
			logger.info("驗證碼失效");
			return result;
		}
		if(!captcha.equalsIgnoreCase(selCaptcha.toString())) {
			result.setData(null);
			result.setFlag(false);
			result.setMsg("驗證碼錯誤");
			logger.info("驗證碼錯誤");
			return result;
		}
		String key = UUID.randomUUID().toString().replace("-", "");
		UserEntity userEntity = new UserEntity();
		userEntity.setId(1);
		userEntity.setName(name);
		userEntity.setPassword(key);
		// 創建 token
		CacheUtils.put(key, userEntity);
		userEntity.setId(1);
		result.setData(userEntity);
		result.setFlag(true);
		return result;
	}
	@RequestMapping("/checkUser")
	public Result checkLogin(HttpServletRequest request) {
		Result result = new Result();
		String cookie = CookieUtil.getCookieByName(request, "kn_token");
		if(null== cookie) {
			result.setData(null);
			result.setFlag(false);
			result.setMsg("你未登錄");
			return result;
		}
		Object object = CacheUtils.get(cookie);
		if(null== object) {
			result.setData(null);
			result.setFlag(false);
			result.setMsg("沒有session");
			return result;
		}
		result.setData(object);
		result.setFlag(true);
		return result;
	}
	
}

                      login.html           

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery-3.2.1.js"></script>
<script type="text/javascript">
$(function(){
	captcha();
})

function captcha() {
	$.ajax({
        type:'post',
        dataType: "json",
        url:"api/captcha",
        data:{ time:new Date()},
        success:function(data){
        	var flag = data.flag;
        	if(flag){
        		var value = data.data.key;
	    		var name = "captcha";
	    		var day = 0;
	    		if(day !== 0){     //當設置的時間等於0時,不設置expires屬性,cookie在瀏覽器關閉後刪除
	    			var expires = day * 24 * 60 * 60 * 1000;
	    			var date = new Date(+new Date()+expires);
	    			document.cookie = name + "=" + escape(value) + ";expires=" + date.toUTCString();
	    		}else{
	    			document.cookie = name + "=" + escape(value);
	    		}
        		$("#captcha").text(data.data.value);
        	}else{
        		$(location).attr('href', 'login.html');
        	}
        },
        error:function(){
        }
    });
}
function login() {
	var userEntity={"id":"1","name":$("#username").val(),"password":$("#password").val(),"captcha":$("#captchas").val()};//json對象
	var userEntityJson = JSON.stringify(userEntity);
	$.ajax({
	    type:'post',
	    dataType: "json",
	    url:"api/login",
	    data:userEntity,
	    beforeSend:function(data){
	    },
	    success:function(data){
	    	var flag = data.flag;
	    	console.log("當前登錄用戶是====="+data.flag);
	    	if(flag){
	    		var value = data.data.password;
	    		var name = "kn_token";
	    		var day = 0;
	    		if(day !== 0){     //當設置的時間等於0時,不設置expires屬性,cookie在瀏覽器關閉後刪除
	    			var expires = day * 24 * 60 * 60 * 1000;
	    			var date = new Date(+new Date()+expires);
	    			document.cookie = name + "=" + escape(value) + ";expires=" + date.toUTCString();
	    		}else{
	    			document.cookie = name + "=" + escape(value);
	    		}
	    		$(location).attr('href', 'user_manager.html');
	    	}else{
	    		$("#msg").html("<font color='red'>"+data.msg+"</font>");
	    	}
	    },
	    complete:function(data){
	    }, 
	    error:function(){
	    	$(location).attr('href', 'login.html');
	    }
	})
}


</script>
</head>
<body>
		<span id="msg"></span><br>
		用戶名<input id="username" type="text" name="name"/><br>
		密碼<input id="password" type="text" name="password"/><br>
		驗證碼<input id="captchas" type="text" name="captcha"/><span id="captcha" onclick="captcha()"></span><br>
		<input type="button" onclick="login()" value="login"/>
</body>
</html>

                     user_manager.html         

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery-3.2.1.js"></script>
<script type="text/javascript">
	$(function(){
		if(window !=top){
			top.location.href=location.href;
		}
		 $.ajax({
		        type:'post',
		        dataType: "json",
		        url:"api/checkUser",
		        data:{ time:new Date()},
		        success:function(data){
		        	var flag = data.flag;
		        	if(flag){
		        	}else{
		        		$(location).attr('href', 'login.html');
		        	}
		        },
		        error:function(){
		        }
		    });
	})
</script>
</head>
<body>

	user_manager.html
	
</body>
</html>

                效果圖:

 

 

 

 

4. 擴展——模擬springboot緩

       4.1  定義註解

          springboot緩存有@Cacheable、@CacheEvict...等註解;相應的創建我們自定義註解@MyCacheable、                                  @MyCacheEvict。。。

MyCacheable.java

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCacheable {
	/**
	 * 緩存名稱
	 * @return
	 */
	String name() default "";
	/**
	 * 緩存的keyGenerator
	 * @return
	 */
	String keyGenerator() default "";
	
	/**
	 * 緩存的過期時間,默認30分鐘
	 * @return
	 */
	long expireTime() default 60*30L;
	
}

        MyCacheEvict.java  

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCacheEvict {
	
	/**
	 * 緩存名稱
	 * @return
	 */
	String name() default "";
	/**
	 * 緩存的keyGenerator
	 * @return
	 */
	String keyGenerator() default "";
	
}

     4.1  定義Aop

           CachePutAop.java             

@SuppressWarnings("all")
@Component
@Aspect
public class CachePutAop {
	
	@Pointcut("@annotation(com.reyco.kn.core.annotation.MyCacheable)")
	public void cachePointcutPut(){
		
	}
	/**
	 * 如果緩存存在直接獲取緩存信息,否則獲取執行方法後,將結果添加到緩存中
	 * @param point		
	 * @throws Throwable
	 */
	@Around("cachePointcutPut()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		// 1.獲取相關數據
		MethodSignature methodSignature = (MethodSignature)point.getSignature();
		// 目標對象
		Class<? extends MethodSignature> clazz = methodSignature.getClass();
		// 目標方法
		Method method = methodSignature.getMethod();
		// 目標參數名
		String[] paramNames = methodSignature.getParameterNames();
		// 目標參數值
		Object[] paramValues = point.getArgs();
		
		// 獲取註解
		MyCacheable myCacheable = method.getAnnotation(MyCacheable.class);
		// key
		String key = "";
		// 3.是否有cacheablePut註解,如果緩存存在直接獲取緩存信息,否則獲取執行方法後,將結果添加到緩存中
		if(null != myCacheable) {
			String cacheName = getCacheName(myCacheable);
			KeyGenerator keyGenerator = getKeyGenerator(myCacheable,clazz,method,paramNames,paramValues);
			// 3.1有註解
			key = getKey(cacheName,keyGenerator);
			// 通過key獲取緩存value
			Object object = CacheUtils.get(key);
			if(null == object) {
				// 緩存中不存在當前緩存,執行目標方法
				object = point.proceed();
				long expireTime = myCacheable.expireTime();
				if(expireTime < 1) {
					CacheUtils.put(key, object);
				}else {
					CacheUtils.put(key, object,expireTime);
				}
			}
			return object;
		}
		return point.proceed();
	}
	/**
	 * 獲取key
	 * @param cacheName	                       緩存名稱
	 * @param keyGeneratorObj   keyGeneratorObj對象
	 * @return
	 */
	private String getKey(String cacheName,KeyGenerator keyGeneratorObj) {
		// String key = cacheName + keyGenerator;
		String key = "";
		// 緩存
		String keyGenerator = keyGeneratorObj.getKeyGenerator();
		key = cacheName + keyGenerator;
		return key;
	}
	/**
	 * 獲取緩存名稱
	 * @param myCacheable
	 * @return
	 */
	private String getCacheName(MyCacheable myCacheable) {
		// 緩存名稱
		String cacheName = "";
		String name = myCacheable.name();
		if(!StringUtils.isBlank(name)) {
			cacheName = name+"::";
		}
		return cacheName;
	}
	/**
	 * 獲取KeyGenerator
	 * @param myCacheEvict
	 * @param clazz
	 * @param method
	 * @param paramNames
	 * @param paramValues
	 * @return
	 */
	private KeyGenerator getKeyGenerator(MyCacheable myCacheable,Class<? extends MethodSignature> clazz,Method method, String[] paramNames, Object[] paramValues) {
		// KeyGenerator
		KeyGenerator keyGeneratorObj = null;
		// 2.1有註解
		String keyGenerator = myCacheable.keyGenerator();
		if(StringUtils.isBlank(keyGenerator)) {
			keyGenerator = createKeyGenerator(clazz,method,paramValues);
		}else {
			// 3.2key是否包含#字符
			if (keyGenerator.contains("#")) {
				// 3.3 獲取KeyGenerator的名稱
				String keyGeneratorName = keyGenerator.substring(1);
				// 3.4 獲取KeyGenerator值
				for (Object paramValue : paramValues) {
					for (int j = 0; j < paramNames.length; j++) {
						if (keyGeneratorName.equals(paramNames[j])) {
							// 如果註解的keyGenerator和參數的名稱一直,取對應參數的值作爲keyGenerator
							keyGenerator = paramValues[j].toString();
						}
					}
				}
			}
		}
		keyGeneratorObj = new KeyGenerator();
		keyGeneratorObj.setKeyGenerator(keyGenerator);
		return keyGeneratorObj;
	}
	/**
	 * key的默認生成器
	 * @param target	目標對象
	 * @param method	目標方法
	 * @param params	目標方法參數
	 * @return
	 */
	private String createKeyGenerator(Object target, Method method, Object... params) {
		// 緩存的key
		StringBuilder sb = new StringBuilder();
		sb.append(target.getClass());
		sb.append(method.getName());
		for (Object obj : params) {
			sb.append(obj.toString());
		}
		return sb.toString();
	}	
}

         CacheDeleteAop.java   

@SuppressWarnings("all")
@Component
@Aspect
public class CacheDeleteAop {
	
	@Pointcut("@annotation(com.reyco.kn.core.annotation.MyCacheEvict)")
	public void cachePointcutDelete() {

	}

	/**
	 * 如果緩存存在直接獲取緩存信息,否則獲取執行方法後,將結果添加到緩存中
	 * 
	 * @param point
	 * @throws Throwable
	 */
	@Around("cachePointcutDelete()")
	public void around(ProceedingJoinPoint point) throws Throwable {
		// 1.獲取相關數據
		MethodSignature methodSignature = (MethodSignature) point.getSignature();
		// 目標對象
		Class<? extends MethodSignature> clazz = methodSignature.getClass();
		// 目標方法
		Method method = methodSignature.getMethod();
		// 獲取註解
		MyCacheEvict myCacheEvict = method.getAnnotation(MyCacheEvict.class);
		// key
		String key = "";
		// 2 是否有cacheableDelete註解,如果有走刪除緩存,否則向下執行
		if (null != myCacheEvict) {
			// 目標參數名
			String[] paramNames = methodSignature.getParameterNames();
			// 目標參數值
			Object[] paramValues = point.getArgs();
			// 緩存名稱
			String cacheName = getCacheName(myCacheEvict);
			// 2.1 獲取key
			KeyGenerator keyGenerator = getKeyGenerator(myCacheEvict,clazz,method,paramNames,paramValues);
			key = getKey(cacheName,keyGenerator);
			// 2.2移除key的緩存
			CacheUtils.remove(key);
		}
		// 3 執行目標方法
		point.proceed();
	}

	/**
	 * 獲取key
	 * @param cacheName	                       緩存名稱
	 * @param keyGeneratorObj   keyGeneratorObj對象
	 * @return
	 */
	private String getKey(String cacheName,KeyGenerator keyGeneratorObj) {
		// String key = cacheName + keyGenerator;
		String key = "";
		// 緩存
		String keyGenerator = keyGeneratorObj.getKeyGenerator();
		key = cacheName + keyGenerator;
		return key;
	}
	/**
	 * 獲取KeyGenerator
	 * @param myCacheEvict
	 * @param clazz
	 * @param method
	 * @param paramNames
	 * @param paramValues
	 * @return
	 */
	private KeyGenerator getKeyGenerator(MyCacheEvict myCacheEvict,Class<? extends MethodSignature> clazz,Method method, String[] paramNames, Object[] paramValues) {
		// KeyGenerator
		KeyGenerator keyGeneratorObj = null;
		// 2.1有註解
		String keyGenerator = myCacheEvict.keyGenerator();
		if(StringUtils.isBlank(keyGenerator)) {
			keyGenerator = createKeyGenerator(clazz,method,paramValues);
		}else {
			// 3.2key是否包含#字符
			if (keyGenerator.contains("#")) {
				// 3.3 獲取KeyGenerator的名稱
				String keyGeneratorName = keyGenerator.substring(1);
				// 3.4 獲取KeyGenerator值
				for (Object paramValue : paramValues) {
					for (int j = 0; j < paramNames.length; j++) {
						if (keyGeneratorName.equals(paramNames[j])) {
							// 如果註解的keyGenerator和參數的名稱一直,取對應參數的值作爲keyGenerator
							keyGenerator = paramValues[j].toString();
						}
					}
				}
			}
		}
		keyGeneratorObj = new KeyGenerator();
		keyGeneratorObj.setKeyGenerator(keyGenerator);
		return keyGeneratorObj;
	}
	
	/**
	 * 獲取緩存名稱
	 * @param myCacheEvict
	 * @return
	 */
	private String getCacheName(MyCacheEvict myCacheEvict) {
		// 緩存名稱
		String cacheName = "";
		String name = myCacheEvict.name();
		if(!StringUtils.isBlank(name)) {
			cacheName = name+"::";
		}
		return cacheName;
	}
	/**
	 * key的默認生成器
	 * 
	 * @param target
	 *            目標對象
	 * @param method
	 *            目標方法
	 * @param params
	 *            目標方法參數
	 * @return
	 */
	private String createKeyGenerator(Object target, Method method, Object... params) {
		// 緩存的KeyGenerator
		StringBuilder sb = new StringBuilder();
		sb.append(target.getClass());
		sb.append(method.getName());
		for (Object obj : params) {
			sb.append(obj.toString());
		}
		return sb.toString();
	}
}

     4.3 測試

        4.3.1 編寫service                  

@Service("userService")
public class UserServiceImpl implements UserService {

	@Autowired
	private UserDao userDao;
	
	@Override
	@MyCacheable(key="#id",name="user",expireTime=20)
	public UserEntity get(Integer id) {
		return userDao.get(id);
	}
	/**
	 * 刪除
	 */
	@Override
	@Transactional(propagation=Propagation.REQUIRED)
	@MyCacheEvict(key="#id",name="user")
	public void delete(Integer id) {
		UserEntity userEntity = new UserEntity();
		userEntity.setId(id);
		userEntity.setState(1);
		userDao.update(userEntity);
	}
	
}

       4.3.2 編寫測試      

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestApplication {

	@Autowired
	UserService userService;
	
	@Test
	public void getGet() {
		userService.get(1);
		userService.get(1);
	}
	
	
	@Test
	public void testDelete() {
		userService.delete(1);
		userService.get(1);
	}
}

       4.3.3 效果         

2019-10-30 17:13:00.193 DEBUG 11464 --- [nio-8081-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public com.reyco.kn.core.domain.Result com.reyco.kn.core.controller.UserController.get(java.lang.Integer)
2019-10-30 17:13:00.239  INFO 11464 --- [nio-8081-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2019-10-30 17:13:00.243  WARN 11464 --- [nio-8081-exec-1] com.zaxxer.hikari.util.DriverDataSource  : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
2019-10-30 17:13:00.900  INFO 11464 --- [nio-8081-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2019-10-30 17:13:00.905 DEBUG 11464 --- [nio-8081-exec-1] com.reyco.kn.core.dao.UserDao.get        : ==>  Preparing: select * from `user` where state=0 and `id`=? 
2019-10-30 17:13:00.925 DEBUG 11464 --- [nio-8081-exec-1] com.reyco.kn.core.dao.UserDao.get        : ==> Parameters: 1(Integer)
2019-10-30 17:13:00.976 DEBUG 11464 --- [nio-8081-exec-1] com.reyco.kn.core.dao.UserDao.get        : <==      Total: 1
2019-10-30 17:13:00.979 DEBUG 11464 --- [nio-8081-exec-1] c.r.k.core.cache.ConcurrentHashMapCache  : key=1,value=UserEntity [id=1,name=admin,password=123456,desc=null,state=0,gmtCreate=null,gmtModified=null],expireTime=1572428580978,添加到緩存中.
2019-10-30 17:13:00.979 DEBUG 11464 --- [nio-8081-exec-1] c.r.k.core.cache.ConcurrentHashMapCache  : 獲取緩存,key=1,value=UserEntity [id=1,name=admin,password=123456,desc=null,state=0,gmtCreate=null,gmtModified=null]
2019-10-30 17:13:01.016 DEBUG 11464 --- [nio-8081-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json]
2019-10-30 17:13:01.017 DEBUG 11464 --- [nio-8081-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Writing [com.reyco.kn.core.domain.Result@5ba33db]
2019-10-30 17:13:01.033 DEBUG 11464 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

       第一次get去查數據庫啦,第二次就使用了緩存。。。      

2019-10-30 17:17:22.046 DEBUG 11464 --- [nio-8081-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public java.lang.String com.reyco.kn.core.controller.UserController.delete(java.lang.Integer)
2019-10-30 17:17:22.110 DEBUG 11464 --- [nio-8081-exec-3] c.r.k.core.cache.ConcurrentHashMapCache  : key=1,value=UserEntity [id=1,name=admin,password=123456,desc=null,state=0,gmtCreate=null,gmtModified=null],已過期,被移除了.
2019-10-30 17:17:22.197 DEBUG 11464 --- [nio-8081-exec-3] com.reyco.kn.core.dao.UserDao.get        : ==>  Preparing: select * from `user` where state=0 and `id`=? 
2019-10-30 17:17:22.197 DEBUG 11464 --- [nio-8081-exec-3] com.reyco.kn.core.dao.UserDao.get        : ==> Parameters: 1(Integer)
2019-10-30 17:17:22.229 DEBUG 11464 --- [nio-8081-exec-3] com.reyco.kn.core.dao.UserDao.get        : <==      Total: 1
2019-10-30 17:17:22.230 DEBUG 11464 --- [nio-8081-exec-3] c.r.k.core.cache.ConcurrentHashMapCache  : key=1,value=UserEntity [id=1,name=admin,password=123456,desc=null,state=0,gmtCreate=null,gmtModified=null],expireTime=1572428842230,添加到緩存中.
2019-10-30 17:17:22.230 DEBUG 11464 --- [nio-8081-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Using 'text/plain', given [*/*] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
2019-10-30 17:17:22.231 DEBUG 11464 --- [nio-8081-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Writing ["刪除成功"]
2019-10-30 17:17:22.233 DEBUG 11464 --- [nio-8081-exec-3] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

         先delete掉緩存後,再去get,爲使用緩存,而是直接查數據庫了。。。

       

            一個簡易的緩存就到這裏了。。。。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章