在上一篇中,使用缓存的示例代码如下:
Resource resource = new ClassPathResource("ehcache-config.xml");
try {
Cache cache = CacheManager.create(resource.getInputStream()).getCache("web");
Element item = cache.get("test");
String js;
if (item != null) {
js = (String) item.getObjectValue();
} else {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 设置日期格式
js = df.format(new Date());
item = new Element("test", js);
cache.put(item);
}
System.out.println(">>:" + js);
} catch (Exception e) {
logger.debug("系统缓存配置文件读取异常" + e.getMessage());
}
是否发现了很多问题:
(1)、客户端(DEMO所在的类)需要依赖具体的缓存框架,如Cache、Element等;
(2)、如果有一天我想更换缓存框架时,则需要修改所有客户端(使用缓存的地方);
(3)、冗余代码太多。
如何解决这个问题?实事上客户端最终需要做的事情只有两件:
(1)、 获取缓存值;
(2)、如果缓存值不存在,则计算值并缓存,以方便下一次读取。
我理解的调用方法是这样的:
String cacheValue = CacheUtil.getCacheValue(String cahceConfig,String cacheKey, CalssObject getCache);
前2个参数简单,第三个参数怎么设计?想到了C#中的委托,用Java实现的话可以是一个接口。即在CacheUtil.getCacheValue方法中,首先读取缓存,如果不存在再使用第三个接口参数计算需要缓存的值,同时做为CacheUtil.getCacheValue的返回值。
当然,第三个接口的实现肯定在客户端完成。
大致思路有了,接下来就是具体实现。
1、规范结构,实现Config、Key
为了规范代码结构,将CacheConfig、CacheKey设计为枚举,两个枚举的代码分别如下:
package com.whowii.website4.libs.cache;
/**
* 缓存配置枚举
*
* @ClassName: CacheConfigEnum
* @Description: TODO
* @author:calvin
* @date 2018年12月4日 下午8:09:12
*/
public enum CacheConfigEnum {
/**
* 缓存配置 db
*/
CONFIG_DB("db"),
/**
* 缓存配置 web
*/
CONFIG_WEB("web");
/**
* 枚举值
*/
private String value;
// 私有构造函数
private CacheConfigEnum(String value) {
this.value = value;
}
/**
* 获取枚举值
* @Title: getValue
* @Description: TODO
* @return
* @throws
* @date 2018年12月4日 下午8:12:26
*/
public String getValue() {
return this.value;
}
}
package com.whowii.website4.libs.cache;
/**
* 缓存键枚举
*
* @ClassName: CacheKeyEnum
* @Description: TODO
* @author:calvin
* @date 2018年12月4日 下午8:08:55
*/
public enum CacheKeyEnum {
/**
* 缓存键 管理模块登录JS
*/
DATA_DICTIONARY("db_data_dictionary"),
/**
* 缓存键 管理模块登录JS
*/
MANAGE_ENTER_JS("manage_enter_js"),
/**
* 缓存键 管理模块框架js
*/
MANAGE_MAIN_JS("manage_main_js"),
/**
* 缓存键 管理模块功能权限树
*/
MANAGE_FUNCTION_TREE("manage_function_tree");
/**
* 枚举值
*/
private String value;
// 私有构造函数
private CacheKeyEnum(String value) {
this.value = value;
}
/**
* 获取枚举值
*
* @Title: getValue
* @Description: TODO
* @return
* @throws
* @date 2018年12月4日 下午8:07:06
*/
public String getValue() {
return this.value;
}
}
这里默认设计了几个常用的缓存配置和缓存键,前者与缓存配置文件相对应,并不是随便写的。后者相当于Map对象的key,根据需要随意设定。
2、计算缓存值的委托(接口)
“委托”、“事件”在C#中使用非常频繁,当然C#中的“事件”是一种特殊的“委托”,在Java中没有delegate关键字,我们用接口实现:
package com.whowii.website4.libs.cache;
/**
* 缓存委托
*
* @ClassName: CacheEvent
* @Description: TODO
* @author:calvin
* @date 2018年12月4日 下午8:12:35
*/
public interface CacheDelegate {
/**
* 获取缓存值
*
* @Title: getCacheValue
* @Description: TODO
* @return
* @throws
* @date 2018年12月4日 下午8:14:01
*/
public Object getCacheValue();
}
3、缓存管理器
有了上面几个对象,接下来就是设计缓存管理器了:
package com.whowii.website4.libs.cache;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
/**
* 缓存工具类
*
* @author calvin
* @date 2015-12-29 下午10:21:43
*/
public class CacheTool {
private static final Logger logger = LogManager.getLogger(CacheTool.class);
private static final String FILE_PATH = "ehcache-config.xml";
@Autowired
private CacheManager cacheManager;
/**
* 构造函数
*/
public CacheTool() {
Resource resource = new ClassPathResource(FILE_PATH);
try {
this.cacheManager = CacheManager.create(resource.getInputStream());
} catch (Exception e) {
logger.error("系统缓存配置文件(" + FILE_PATH + ") 读取异常" + e.getMessage());
}
}
/**
* 获取缓存值
*
* @param config
* @param key
* @param event
* @return
*/
public Object getCacheValue(CacheConfigEnum config, CacheKeyEnum key, CacheDelegate event) {
Cache cache = this.cacheManager.getCache(config.getValue());
Element item = cache.get(key.getValue());
Object result = null;
if (item != null) {
result = item.getObjectValue();
} else {
result = event.getCacheValue();
item = new Element(key.getValue(), result);
cache.put(item);
}
return result;
}
}
可能有人会说“为什么此类中还要定义缓存配置文件名?“,前文说过了,正常情况下springboot官方建设使用注解的方式编码,这里只是为了解决在特定场景的问题,当然你可能并不需要,也可以继续使用注解方式。
此类中只有一个方法,即通过指定的配置和键获取缓存值,如果存在则返回,否则使用缓存委托计算缓存值, 在保存后直接返回。
4、缓存工具
实际上,我想把CacheTool命名为CacheManage,但与Ehcache重名,虽然可以通过完全限定名定义和使用,但不太方便。所以命名为CacheTool。
这个类才是真正的工具类,即通过静态方法直接读取缓存值,也是客户端使用的唯一入口:
package com.whowii.website4.utils;
import com.whowii.website4.libs.cache.CacheConfigEnum;
import com.whowii.website4.libs.cache.CacheDelegate;
import com.whowii.website4.libs.cache.CacheKeyEnum;
import com.whowii.website4.libs.cache.CacheTool;
/**
* 缓存工具类
*
* @ClassName: CacheUtil
* @Description: TODO
* @author:calvin
* @date 2018年12月4日 下午7:53:51
*/
public class CacheUtil {
private static CacheTool cache;
/**
* 静态初始化
*/
static {
if (CacheUtil.cache == null) {
synchronized (CacheUtil.class) {
if (CacheUtil.cache == null) {
CacheUtil.cache = new CacheTool();
}
}
}
}
/**
* 获取缓存值
*
* @Title: getCacheValue
* @Description: TODO
* @param config
* @param key
* @param event
* @return
* @throws
* @date 2018年12月4日 下午7:46:47
*/
public static Object getCacheValue(CacheConfigEnum config, CacheKeyEnum key, CacheDelegate event) {
return CacheUtil.cache.getCacheValue(config, key, event);
}
}
静态初始化类似C#中的静态构造函数,其中的代码是为了解决多线程可能造成的多次赋值。
唯一的方法就是调用缓存管理器CacheTool,以便获取缓存。
5、客户端使用
再次修改测试类(DemoController)如下:
package com.whowii.website4.admin.controller;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.whowii.website4.admin.service.DemoService;
import com.whowii.website4.libs.cache.CacheConfigEnum;
import com.whowii.website4.libs.cache.CacheDelegate;
import com.whowii.website4.libs.cache.CacheKeyEnum;
import com.whowii.website4.utils.CacheUtil;
@Controller
@RequestMapping("/demo")
public class DemoController {
private static final Logger logger = LogManager.getLogger(DemoController.class);
@Autowired
private DemoService demoService;
@GetMapping("/index")
public String index(Model model) {
String js = (String) CacheUtil.getCacheValue(CacheConfigEnum.CONFIG_WEB,CacheKeyEnum.MANAGE_ENTER_JS,
new CacheDelegate() {
@Override
public Object getCacheValue() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 设置日期格式
System.out.println("=========>" + df.format(new Date()));
return df.format(new Date());
}
});
System.out.println(">>:" + js);
return "/admin/demo/demo-index";
}
}
这样看客户端是不是清爽多了?客户端并不需要知道具体的缓存框架是什么,减少了耦合。即使后期项目变更,需要使用其他缓存框架(如集群式缓存)时,只需要修改缓存管理器CacheTool,所有客户端及缓存工具CacheUtil无需改动。
启动应用,打开浏览器访问“http://127.0.0.1:8080/demo/index”,多次刷新页面后,MyEclise控制台输出如下: