基于 Spring Boot 的 SSM 环境整合十一:缓存工具类的优化设计

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xz2001/article/details/84798213

在上一篇中,使用缓存的示例代码如下:

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控制台输出如下:

猜你喜欢

转载自blog.csdn.net/xz2001/article/details/84798213
今日推荐