对Guava Cache的封装和使用(包括一个管理页面实现了查看统计信息、情况、查看记录等)

本文详细介绍了如何使用GuavaCache作为本地缓存策略,以提高接口处理速度,包括缓存实现、封装类设计、管理界面功能及优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于项目的实际情况,需要缓存一些比较不经常改动的数据在本地服务器中,以提高接口处理的速度。决定采用Guava Cache之后,整理了一些具体需求:

  1. 由于要缓存的key-value对比较多,需要一个封装好的类被继承,子类可以简单的实现把key-value缓存到Guava Cache中;
  2. 需要定义一个接口,简单的定义一个get(K key)方法,方便使用;
  3. 需要有一个管理界面,统计缓存的命中率、记录数,以便以后做出相应的调整;
  4. 需要有一个管理界面,重设、清空缓存中的数据,或使缓存中的数据失效,以强行让服务器重新从数据库获取数据,并记录重置的时间;
  5. 需要有一个管理界面,分页查看缓存中的具体内容。

现在,该系统已经实现,并已经在正式环境中运行了一段时间,日均总命中次数超过一百万,大部分缓存的命中率在98%以上,为某些接口的请求节省了一半的时间。

Guava Cache简介:

Guava Cache提供了一种把数据(key-value对)缓存到本地(JVM)内存中的机制,适用于很少会改动的数据,比如地区信息、系统配置、字典数据,等。Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。

本文介绍了一种对Guava LoadingCache的封装使用,并提供管理页面的实现。

首先,介绍一些Guava Cache的基本概念:

Guava提供两种不同的方法来加载数据:
  • CacheLoader:在build cache的时候定义一个CacheLoader来获取数据,适用的情况:有固定的方式可以根据key来加载或计算value的值,比如从数据库中获取数据
  • Callable:在get的时候传入一个Callable对象,适用的情况:如果从缓存中获取不到数据,则另外计算一个出来,并把计算结果加入到缓存中
另外,还可以使用cache.put(key, value)方法直接向缓存中插入值,但不推荐使用,因为这样会多了一步操作。

缓存回收方式:
1、基于容量的回收(size-based eviction),有两种方式,接近最大的size或weight时回收:
  • 基于maximumSize(long):一个数据项占用一个size单位,适用于value是固定大小的情况
  • 基于maximumWeight(long):对不同的数据项计算weight,适用于value不定大小的情况,比如value为Map类型时,可以把map.size()作为weight
2、定时回收(Timed Eviction)
  • expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写,则回收。
  • expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。
3、基于引用的回收(Reference-based Eviction),通过使用弱引用的键或值、或软引用的值,把缓存设置为允许垃圾回收器回收:
  • CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被GC回收
  • CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被GC回收
  • CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。影响性能,不推荐使用。
4、显式清除(invalidate)
  • 个别清除:Cache.invalidate(key)
  • 批量清除:Cache.invalidateAll(keys)
  • 清除所有缓存项:Cache.invalidateAll()

什么时候发生缓存清理:
使用CacheBuilder构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。它是在写操作时顺带做少量的维护工作(清理);如果写操作太少,读操作的时候也会进行少量维护工作。

基于容量的回收原则:
基本原则是LRU,但是按照每个Segment来清除的。比如:
一个maximumSize为100的Cache,concurrencyLevel=4,则如果开始清除缓存时,那些segment中size>25的会被优先清除掉只剩下25个。

移除监听器(Removal Listener):
通过 CacheBuilder.removalListener(RemovalListener),可以声明一个监听器,以便缓存项被移除时做一些额外操作,RemovalListener会获取移除通知[RemovalNotification],里面包含移除原因[RemovalCause]、键和值。

注:耗性能。可以使用 RemovalListeners.asynchronous(RemovalListener, Executor)定义监听器为异步操作。

统计功能:
CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回CacheStats对象以提供一些统计信息。具体信息可以查看CacheStats类的定义。

asMap视图:
asMap视图提供了缓存的ConcurrentMap形式,但它的get方法不会往缓存中加入数据,实质上等同于 cache.getIfPresent(key)。可以用它来遍历查看缓存中的所有数据。


下面介绍对Guava Cache进行封装使用的具体方法:

使用的设计模式:策略模式(Strategy)
关于这种设计模式,具体请参考: http://zz563143188.iteye.com/blog/1847029 (第13种)
这种设计模式的关系图如下:


总策略:利用Guava Cache来存放我们自己的数据。
图中3+1种角色和代码的对应关系如下:
  1. Context:Service.java,(策略的使用者)
  2. Strategy:ILocalCache.java,(定义策略的接口)
  3. ConcreteStrategy:LCAreaIdToArea.java,其他ILocalCache实现类 ……,(实现策略的类)
  4. 辅助类:GuavaAbstractLoadingCache.java,(实现策略的辅助类,就是封装了Guava Cache的一个类)

各角色对应的具体功能如下:
  1. Service:cache的使用者
  2. Cache接口:定义一个get()方法,通过key获取value
  3. Cache实现类:利用辅助类,实现Cache接口的get()方法
  4. Guava Cache实现辅助类:封装了对Guava Cache的利用,包括cache的创建、从数据源获取数据、定义过时策略、等
除了上面的核心功能模块,其他辅助的功能模块如下:
Cache管理类:封装了对所有实现类进行管理的一些方法,包括清空Cache中的数据、查看数据、查看统计信息、等
Cache Controller:调用Cache管理类,为管理页面提供Web HTTP接口
Cache管理页面:Web页面,用于查看Cache列表、统计信息、数据,清空缓存,等

各模块的具体代码,代码中已经包括了比较详尽的注解:

主要的依赖包:

  1. <dependency>
  2. <groupId>com.google.guava</groupId>
  3. <artifactId>guava</artifactId>
  4. <version>18.0</version>
  5. </dependency>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>18.0</version>
		</dependency>

Cache接口:定义一个get()方法,通过key获取value

  1. /**
  2. * 本地缓存接口
  3. * @author XuJijun
  4. *
  5. * @param <K> Key的类型
  6. * @param <V> Value的类型
  7. */
  8. public interface ILocalCache <K, V> {
  9. /**
  10. * 从缓存中获取数据
  11. * @param key
  12. * @return value
  13. */
  14. public V get(K key);
  15. }
/**
 * 本地缓存接口
 * @author XuJijun
 *
 * @param <K> Key的类型
 * @param <V> Value的类型
 */
public interface ILocalCache <K, V> {
	
	/**
	 * 从缓存中获取数据
	 * @param key
	 * @return value
	 */
	public V get(K key);
}

Guava Cache实现辅助类:封装了对Guava Cache的利用,包括cache的创建、从数据源获取数据、定义过时策略、等
  1. package com.xjj.cache.guava;
  2. /**
  3. * 抽象Guava缓存类、缓存模板。
  4. * 子类需要实现fetchData(key),从数据库或其他数据源(如Redis)中获取数据。
  5. * 子类调用getValue(key)方法,从缓存中获取数据,并处理不同的异常,比如value为null时的InvalidCacheLoadException异常。
  6. *
  7. * @author XuJijun
  8. * @Date 2015-05-18
  9. *
  10. * @param <K> key 类型
  11. * @param <V> value 类型
  12. */
  13. public abstract class GuavaAbstractLoadingCache <K, V> {
  14. protected final Logger logger = LoggerFactory.getLogger(this.getClass());
  15. //用于初始化cache的参数及其缺省值
  16. private int maximumSize = 1000; //最大缓存条数,子类在构造方法中调用setMaximumSize(int size)来更改
  17. private int expireAfterWriteDuration = 60; //数据存在时长,子类在构造方法中调用setExpireAfterWriteDuration(int duration)来更改
  18. private TimeUnit timeUnit = TimeUnit.MINUTES; //时间单位(分钟)
  19. private Date resetTime; //Cache初始化或被重置的时间
  20. private long highestSize=0; //历史最高记录数
  21. private Date highestTime; //创造历史记录的时间
  22. private LoadingCache<K, V> cache;
  23. /**
  24. * 通过调用getCache().get(key)来获取数据
  25. * @return cache
  26. */
  27. public LoadingCache<K, V> getCache() {
  28. if(cache == null){ //使用双重校验锁保证只有一个cache实例
  29. synchronized (this) {
  30. if(cache == null){
  31. cache = CacheBuilder.newBuilder().maximumSize(maximumSize) //缓存数据的最大条目,也可以使用.maximumWeight(weight)代替
  32. .expireAfterWrite(expireAfterWriteDuration, timeUnit) //数据被创建多久后被移除
  33. .recordStats() //启用统计
  34. .build(new CacheLoader<K, V>() {
  35. @Override
  36. public V load(K key) throws Exception {
  37. return fetchData(key);
  38. }
  39. });
  40. this.resetTime = new Date();
  41. this.highestTime = new Date();
  42. logger.debug("本地缓存{}初始化成功", this.getClass().getSimpleName());
  43. }
  44. }
  45. }
  46. return cache;
  47. }
  48. /**
  49. * 根据key从数据库或其他数据源中获取一个value,并被自动保存到缓存中。
  50. * @param key
  51. * @return value,连同key一起被加载到缓存中的。
  52. */
  53. protected abstract V fetchData(K key);
  54. /**
  55. * 从缓存中获取数据(第一次自动调用fetchData从外部获取数据),并处理异常
  56. * @param key
  57. * @return Value
  58. * @throws ExecutionException
  59. */
  60. protected V getValue(K key) throws ExecutionException {
  61. V result = getCache().get(key);
  62. if(getCache().size() > highestSize){
  63. highestSize = getCache().size();
  64. highestTime = new Date();
  65. }
  66. return result;
  67. }
  68. public long getHighestSize() {
  69. return highestSize;
  70. }
  71. public Date getHighestTime() {
  72. return highestTime;
  73. }
  74. public Date getResetTime() {
  75. return resetTime;
  76. }
  77. public void setResetTime(Date resetTime) {
  78. this.resetTime = resetTime;
  79. }
  80. public int getMaximumSize() {
  81. return maximumSize;
  82. }
  83. public int getExpireAfterWriteDuration() {
  84. return expireAfterWriteDuration;
  85. }
  86. /**
  87. * 设置最大缓存条数
  88. * @param maximumSize
  89. */
  90. public void setMaximumSize(int maximumSize) {
  91. this.maximumSize = maximumSize;
  92. }
  93. /**
  94. * 设置数据存在时长(分钟)
  95. * @param expireAfterWriteDuration
  96. */
  97. public void setExpireAfterWriteDuration(int expireAfterWriteDuration) {
  98. this.expireAfterWriteDuration = expireAfterWriteDuration;
  99. }
  100. }
package com.xjj.cache.guava;

/**
 * 抽象Guava缓存类、缓存模板。
 * 子类需要实现fetchData(key),从数据库或其他数据源(如Redis)中获取数据。
 * 子类调用getValue(key)方法,从缓存中获取数据,并处理不同的异常,比如value为null时的InvalidCacheLoadException异常。
 * 
 * @author XuJijun
 * @Date 2015-05-18
 *
 * @param <K> key 类型
 * @param <V> value 类型
 */
public abstract class GuavaAbstractLoadingCache <K, V> {
	protected final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	//用于初始化cache的参数及其缺省值
	private int maximumSize = 1000;					//最大缓存条数,子类在构造方法中调用setMaximumSize(int size)来更改
	private int expireAfterWriteDuration = 60;		//数据存在时长,子类在构造方法中调用setExpireAfterWriteDuration(int duration)来更改
	private TimeUnit timeUnit = TimeUnit.MINUTES;	//时间单位(分钟)
	
	private Date resetTime;		//Cache初始化或被重置的时间
	private long highestSize=0;	//历史最高记录数
	private Date highestTime;	//创造历史记录的时间
	
	private LoadingCache<K, V> cache;
	
	/**
	 * 通过调用getCache().get(key)来获取数据 
	 * @return cache
	 */
	public LoadingCache<K, V> getCache() {
		if(cache == null){	//使用双重校验锁保证只有一个cache实例
			synchronized (this) {
				if(cache == null){
					cache = CacheBuilder.newBuilder().maximumSize(maximumSize)		//缓存数据的最大条目,也可以使用.maximumWeight(weight)代替
							.expireAfterWrite(expireAfterWriteDuration, timeUnit)	//数据被创建多久后被移除
							.recordStats()											//启用统计
							.build(new CacheLoader<K, V>() {
								@Override
								public V load(K key) throws Exception {
									return fetchData(key);
								}
							});
					this.resetTime = new Date();
					this.highestTime = new Date();
					logger.debug("本地缓存{}初始化成功", this.getClass().getSimpleName());
				}
			}
		}
		
		return cache;
	}
	
	/**
	 * 根据key从数据库或其他数据源中获取一个value,并被自动保存到缓存中。
	 * @param key
	 * @return value,连同key一起被加载到缓存中的。 
	 */
	protected abstract V fetchData(K key);

	/**
	 * 从缓存中获取数据(第一次自动调用fetchData从外部获取数据),并处理异常
	 * @param key
	 * @return Value
	 * @throws ExecutionException 
	 */
	protected V getValue(K key) throws ExecutionException {
		V result = getCache().get(key);
		if(getCache().size() > highestSize){
			highestSize = getCache().size();
			highestTime = new Date();
		}

		return result;
	}

	public long getHighestSize() {
		return highestSize;
	}
	
	public Date getHighestTime() {
		return highestTime;
	}
	
	public Date getResetTime() {
		return resetTime;
	}

	public void setResetTime(Date resetTime) {
		this.resetTime = resetTime;
	}

	public int getMaximumSize() {
		return maximumSize;
	}

	public int getExpireAfterWriteDuration() {
		return expireAfterWriteDuration;
	}

	/**
	 * 设置最大缓存条数
	 * @param maximumSize
	 */
	public void setMaximumSize(int maximumSize) {
		this.maximumSize = maximumSize;
	}

	/**
	 * 设置数据存在时长(分钟)
	 * @param expireAfterWriteDuration
	 */
	public void setExpireAfterWriteDuration(int expireAfterWriteDuration) {
		this.expireAfterWriteDuration = expireAfterWriteDuration;
	}
}

Cache实现类:利用辅助类,实现Cache接口的get()方法,(以一个areaId -> Area为例子)
  1. package com.xjj.entity;
  2. public class Area {
  3. private int id;
  4. private int parentCode;
  5. private String name;
  6. private int code;
  7. private String pinyin;
  8. private int type;
  9. public char getFirstLetter(){
  10. return pinyin.charAt(0);
  11. }
  12. //省略其他getter和setter
  13. }
  14. package com.xjj.cache.local.impl;
  15. /**
  16. * 本地缓存:areaId -> Area
  17. * @author XuJijun
  18. *
  19. */
  20. @Component
  21. public class LCAreaIdToArea extends GuavaAbstractLoadingCache<Integer, Area> implements ILocalCache<Integer, Area> {
  22. //@Autowired
  23. //private AreasDAO areasDAO;
  24. //由Spring来维持单例模式
  25. private LCAreaIdToArea(){
  26. setMaximumSize(3000); //最大缓存条数
  27. }
  28. @Override
  29. public Area get(Integer key) {
  30. try {
  31. return getValue(key);
  32. } catch (Exception e) {
  33. logger.error("无法根据areaId={}获取Area,可能是数据库中无该记录。", key ,e);
  34. return null;
  35. }
  36. }
  37. /**
  38. * 从数据库中获取数据
  39. */
  40. @Override
  41. protected Area fetchData(Integer key) {
  42. logger.debug("测试:正在从数据库中获取area,area id={}", key);
  43. //return areasDAO.getAreaById(key);
  44. //测试专用,实际项目使用areaDao从数据库中获取数据
  45. Area a = new Area();
  46. a.setCode(key);
  47. a.setId(key);
  48. a.setName("地区:"+key);
  49. a.setParentCode(Integer.valueOf(key.toString().substring(0, key.toString().length()-3)));
  50. a.setPinyin("pinyin:"+key);
  51. a.setType(AreaType.CITY.getValue());
  52. return a;
  53. }
  54. }
package com.xjj.entity;

public class Area {
	private int id;
	private int parentCode;
	private String name;
	private int code;
	private String pinyin;
	private int type;

	public char getFirstLetter(){
		return pinyin.charAt(0);
	}

//省略其他getter和setter
}

package com.xjj.cache.local.impl;

/**
 * 本地缓存:areaId -> Area
 * @author XuJijun
 *
 */
@Component
public class LCAreaIdToArea extends GuavaAbstractLoadingCache<Integer, Area> implements ILocalCache<Integer, Area> {
	//@Autowired
	//private AreasDAO areasDAO;
	
	//由Spring来维持单例模式
	
	private LCAreaIdToArea(){
		setMaximumSize(3000); //最大缓存条数
	}
	
	@Override
	public Area get(Integer key) {
		try {
			return getValue(key);
		} catch (Exception e) {
			logger.error("无法根据areaId={}获取Area,可能是数据库中无该记录。", key ,e);
			return null;
		}
	}

	/**
	 * 从数据库中获取数据
	 */
	@Override
	protected Area fetchData(Integer key) {
		logger.debug("测试:正在从数据库中获取area,area id={}", key);
		//return areasDAO.getAreaById(key);
		//测试专用,实际项目使用areaDao从数据库中获取数据
		Area a = new Area();
		a.setCode(key);
		a.setId(key);
		a.setName("地区:"+key);
		a.setParentCode(Integer.valueOf(key.toString().substring(0, key.toString().length()-3)));
		a.setPinyin("pinyin:"+key);
		a.setType(AreaType.CITY.getValue());
		
		return a;
	}
}

Service:cache的使用者
  1. /**
  2. * Area相关方法,使用缓存
  3. * @author XuJijun
  4. *
  5. */
  6. @Service
  7. public class AreaService implements IAreaService {
  8. @Resource(name="LCAreaIdToArea")
  9. ILocalCache<Integer, Area> lCAreaIdToArea;
  10. /**
  11. * 根据areaId获取Area
  12. * @param areaId
  13. * @return Area
  14. */
  15. @Override
  16. public Area getAreaById(int areaId) {
  17. return lCAreaIdToArea.get(areaId);
  18. }
  19. }
/**
 * Area相关方法,使用缓存
 * @author XuJijun
 *
 */
@Service
public class AreaService implements IAreaService {
	@Resource(name="LCAreaIdToArea")
	ILocalCache<Integer, Area> lCAreaIdToArea;

	/**
	 * 根据areaId获取Area
	 * @param areaId
	 * @return Area
	 */	
	@Override
	public Area getAreaById(int areaId) {
		return lCAreaIdToArea.get(areaId);
	}

}

Cache管理类:封装了对所有实现类进行管理的一些方法,包括清空Cache中的数据、查看数据、查看统计信息、等
代码中所涉及到的其他类(SpringContextUtil、PageParams、PageResult)请参考源代码: https://github.com/xujijun/MyJavaStudio
  1. package com.xjj.cache.guava;
  2. /**
  3. * Guava缓存监视和管理工具
  4. * @author XuJijun
  5. *
  6. */
  7. public class GuavaCacheManager {
  8. //保存一个Map: cacheName -> cache Object,以便根据cacheName获取Guava cache对象
  9. private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> cacheNameToObjectMap = null;
  10. /**
  11. * 获取所有GuavaAbstractLoadingCache子类的实例,即所有的Guava Cache对象
  12. * @return
  13. */
  14. @SuppressWarnings("unchecked")
  15. private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> getCacheMap(){
  16. if(cacheNameToObjectMap==null){
  17. cacheNameToObjectMap = (Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>>) SpringContextUtil.getBeanOfType(GuavaAbstractLoadingCache.class);
  18. }
  19. return cacheNameToObjectMap;
  20. }
  21. /**
  22. * 根据cacheName获取cache对象
  23. * @param cacheName
  24. * @return
  25. */
  26. private static GuavaAbstractLoadingCache<Object, Object> getCacheByName(String cacheName){
  27. return (GuavaAbstractLoadingCache<Object, Object>) getCacheMap().get(cacheName);
  28. }
  29. /**
  30. * 获取所有缓存的名字(即缓存实现类的名称)
  31. * @return
  32. */
  33. public static Set<String> getCacheNames() {
  34. return getCacheMap().keySet();
  35. }
  36. /**
  37. * 返回所有缓存的统计数据
  38. * @return List<Map<统计指标,统计数据>>
  39. */
  40. public static ArrayList<Map<String, Object>> getAllCacheStats() {
  41. Map<String, ? extends Object> cacheMap = getCacheMap();
  42. List<String> cacheNameList = new ArrayList<>(cacheMap.keySet());
  43. Collections.sort(cacheNameList);//按照字母排序
  44. //遍历所有缓存,获取统计数据
  45. ArrayList<Map<String, Object>> list = new ArrayList<>();
  46. for(String cacheName : cacheNameList){
  47. list.add(getCacheStatsToMap(cacheName));
  48. }
  49. return list;
  50. }
  51. /**
  52. * 返回一个缓存的统计数据
  53. * @param cacheName
  54. * @return Map<统计指标,统计数据>
  55. */
  56. private static Map<String, Object> getCacheStatsToMap(String cacheName) {
  57. Map<String, Object> map = new LinkedHashMap<>();
  58. GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
  59. CacheStats cs = cache.getCache().stats();
  60. NumberFormat percent = NumberFormat.getPercentInstance(); // 建立百分比格式化用
  61. percent.setMaximumFractionDigits(1); // 百分比小数点后的位数
  62. map.put("cacheName", cacheName);
  63. map.put("size", cache.getCache().size());
  64. map.put("maximumSize", cache.getMaximumSize());
  65. map.put("survivalDuration", cache.getExpireAfterWriteDuration());
  66. map.put("hitCount", cs.hitCount());
  67. map.put("hitRate", percent.format(cs.hitRate()));
  68. map.put("missRate", percent.format(cs.missRate()));
  69. map.put("loadSuccessCount", cs.loadSuccessCount());
  70. map.put("loadExceptionCount", cs.loadExceptionCount());
  71. map.put("totalLoadTime", cs.totalLoadTime()/1000000); //ms
  72. SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  73. if(cache.getResetTime()!=null){
  74. map.put("resetTime", df.format(cache.getResetTime()));
  75. }
  76. map.put("highestSize", cache.getHighestSize());
  77. if(cache.getHighestTime()!=null){
  78. map.put("highestTime", df.format(cache.getHighestTime()));
  79. }
  80. return map;
  81. }
  82. /**
  83. * 根据cacheName清空缓存数据
  84. * @param cacheName
  85. */
  86. public static void resetCache(String cacheName){
  87. GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
  88. cache.getCache().invalidateAll();
  89. cache.setResetTime(new Date());
  90. }
  91. /**
  92. * 分页获得缓存中的数据
  93. * @param pageParams
  94. * @return
  95. */
  96. public static PageResult<Object> queryDataByPage(PageParams<Object> pageParams) {
  97. PageResult<Object> data = new PageResult<>(pageParams);
  98. GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName((String) pageParams.getParams().get("cacheName"));
  99. ConcurrentMap<Object, Object> cacheMap = cache.getCache().asMap();
  100. data.setTotalRecord(cacheMap.size());
  101. data.setTotalPage((cacheMap.size()-1)/pageParams.getPageSize()+1);
  102. //遍历
  103. Iterator<Entry<Object, Object>> entries = cacheMap.entrySet().iterator();
  104. int startPos = pageParams.getStartPos()-1;
  105. int endPos = pageParams.getEndPos()-1;
  106. int i=0;
  107. Map<Object, Object> resultMap = new LinkedHashMap<>();
  108. while (entries.hasNext()) {
  109. Map.Entry<Object, Object> entry = entries.next();
  110. if(i>endPos){
  111. break;
  112. }
  113. if(i>=startPos){
  114. resultMap.put(entry.getKey(), entry.getValue());
  115. }
  116. i++;
  117. }
  118. List<Object> resultList = new ArrayList<>();
  119. resultList.add(resultMap);
  120. data.setResults(resultList);
  121. return data;
  122. }
  123. }
package com.xjj.cache.guava;

/**
 * Guava缓存监视和管理工具
 * @author XuJijun
 *
 */
public class GuavaCacheManager {
	//保存一个Map: cacheName -> cache Object,以便根据cacheName获取Guava cache对象
	private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> cacheNameToObjectMap = null;

	/**
	 * 获取所有GuavaAbstractLoadingCache子类的实例,即所有的Guava Cache对象
	 * @return
	 */
	
	@SuppressWarnings("unchecked")
	private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> getCacheMap(){
		if(cacheNameToObjectMap==null){
			cacheNameToObjectMap = (Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>>) SpringContextUtil.getBeanOfType(GuavaAbstractLoadingCache.class);
		}
		return cacheNameToObjectMap;
		
	}
	
	/**
	 *	根据cacheName获取cache对象 
	 * @param cacheName
	 * @return
	 */
	private static GuavaAbstractLoadingCache<Object, Object> getCacheByName(String cacheName){
		return (GuavaAbstractLoadingCache<Object, Object>) getCacheMap().get(cacheName);
	}
	
	/**
	 * 获取所有缓存的名字(即缓存实现类的名称)
	 * @return
	 */
	public static Set<String> getCacheNames() {
		return getCacheMap().keySet();
	}
	
	/**
	 * 返回所有缓存的统计数据
	 * @return List<Map<统计指标,统计数据>>
	 */
	public static ArrayList<Map<String, Object>> getAllCacheStats() {
		
		Map<String, ? extends Object> cacheMap = getCacheMap();
		List<String> cacheNameList = new ArrayList<>(cacheMap.keySet());
		Collections.sort(cacheNameList);//按照字母排序

		//遍历所有缓存,获取统计数据
		ArrayList<Map<String, Object>> list = new ArrayList<>();
		for(String cacheName : cacheNameList){
			list.add(getCacheStatsToMap(cacheName));
		}
		
		return list;
	}
	
	/**
	 * 返回一个缓存的统计数据
	 * @param cacheName
	 * @return Map<统计指标,统计数据>
	 */
	private static Map<String, Object> getCacheStatsToMap(String cacheName) {
		Map<String, Object> map =  new LinkedHashMap<>();
		GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
		CacheStats cs = cache.getCache().stats();
		NumberFormat percent = NumberFormat.getPercentInstance(); // 建立百分比格式化用
		percent.setMaximumFractionDigits(1); // 百分比小数点后的位数
		map.put("cacheName", cacheName);
		map.put("size", cache.getCache().size());
		map.put("maximumSize", cache.getMaximumSize());
		map.put("survivalDuration", cache.getExpireAfterWriteDuration());
		map.put("hitCount", cs.hitCount());
		map.put("hitRate", percent.format(cs.hitRate()));
		map.put("missRate", percent.format(cs.missRate()));
		map.put("loadSuccessCount", cs.loadSuccessCount());
		map.put("loadExceptionCount", cs.loadExceptionCount());
		map.put("totalLoadTime", cs.totalLoadTime()/1000000); 		//ms
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		if(cache.getResetTime()!=null){
			map.put("resetTime", df.format(cache.getResetTime()));
		}
		map.put("highestSize", cache.getHighestSize());
		if(cache.getHighestTime()!=null){
			map.put("highestTime", df.format(cache.getHighestTime()));	
		}
		
		return map;
	}
	
	/**
	 * 根据cacheName清空缓存数据
	 * @param cacheName
	 */
	public static void resetCache(String cacheName){
		GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
		cache.getCache().invalidateAll();
		cache.setResetTime(new Date());
	}

	/**
	 * 分页获得缓存中的数据 
	 * @param pageParams
	 * @return
	 */
	public static PageResult<Object> queryDataByPage(PageParams<Object> pageParams) {
		PageResult<Object> data = new PageResult<>(pageParams);
		
		GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName((String) pageParams.getParams().get("cacheName"));
		ConcurrentMap<Object, Object> cacheMap = cache.getCache().asMap();
		data.setTotalRecord(cacheMap.size());
		data.setTotalPage((cacheMap.size()-1)/pageParams.getPageSize()+1);
		
		//遍历
		Iterator<Entry<Object, Object>> entries = cacheMap.entrySet().iterator();
		int startPos = pageParams.getStartPos()-1;
		int endPos = pageParams.getEndPos()-1; 
		int i=0;
		Map<Object, Object> resultMap = new LinkedHashMap<>();
		while (entries.hasNext()) {
			Map.Entry<Object, Object> entry = entries.next();
			if(i>endPos){
				break;
			}
			
			if(i>=startPos){
				resultMap.put(entry.getKey(), entry.getValue());
			}
			
			i++;
		}
		List<Object> resultList = new ArrayList<>();
		resultList.add(resultMap);
		data.setResults(resultList);
		return data;
	}
}

Cache Controller:调用Cache管理类,为管理页面提供Web HTTP接口
  1. package com.xjj.web.controller;
  2. /**
  3. * 本地缓存管理接口:统计信息查询、重置数据……等
  4. * @author XuJijun
  5. *
  6. */
  7. @RestController
  8. @RequestMapping("/cache/admin")
  9. public class CacheAdminController {
  10. /**
  11. * 查询cache统计信息
  12. * @param cacheName
  13. * @return cache统计信息
  14. */
  15. @RequestMapping(value = "/stats", method = RequestMethod.POST)
  16. public JsonResult cacheStats(String cacheName) {
  17. JsonResult jsonResult = new JsonResult();
  18. //暂时只支持获取全部
  19. switch (cacheName) {
  20. case "*":
  21. jsonResult.setData(GuavaCacheManager.getAllCacheStats());
  22. jsonResult.setMessage("成功获取了所有的cache!");
  23. break;
  24. default:
  25. break;
  26. }
  27. return jsonResult;
  28. }
  29. /**
  30. * 清空缓存数据、并返回清空后的统计信息
  31. * @param cacheName
  32. * @return
  33. */
  34. @RequestMapping(value = "/reset", method = RequestMethod.POST)
  35. public JsonResult cacheReset(String cacheName) {
  36. JsonResult jsonResult = new JsonResult();
  37. GuavaCacheManager.resetCache(cacheName);
  38. jsonResult.setMessage("已经成功重置了" + cacheName + "!");
  39. return jsonResult;
  40. }
  41. /**
  42. * 返回所有的本地缓存统计信息
  43. * @return
  44. */
  45. @RequestMapping(value = "/stats/all", method = RequestMethod.POST)
  46. public JsonResult cacheStatsAll() {
  47. return cacheStats("*");
  48. }
  49. /**
  50. * 分页查询数据详情
  51. * @param pageSize
  52. * @param pageNo
  53. * @param cacheName
  54. * @return
  55. */
  56. @RequestMapping(value = "/queryDataByPage", method = RequestMethod.POST)
  57. public PageResult<Object> queryDataByPage(@RequestParam Map<String, String> params){
  58. int pageSize = Integer.valueOf(params.get("pageSize"));
  59. int pageNo = Integer.valueOf(params.get("pageNo"));
  60. String cacheName = params.get("cacheName");
  61. PageParams<Object> page = new PageParams<>();
  62. page.setPageSize(pageSize);
  63. page.setPageNo(pageNo);
  64. Map<String, Object> param = new HashMap<>();
  65. param.put("cacheName", cacheName);
  66. page.setParams(param);
  67. return GuavaCacheManager.queryDataByPage(page);
  68. }
  69. }
package com.xjj.web.controller;

/**
 * 本地缓存管理接口:统计信息查询、重置数据……等
 * @author XuJijun
 *
 */
@RestController
@RequestMapping("/cache/admin")
public class CacheAdminController {
	
	/**
	 * 查询cache统计信息
	 * @param cacheName
	 * @return cache统计信息
	 */
	@RequestMapping(value = "/stats", method = RequestMethod.POST)
	public JsonResult cacheStats(String cacheName) {
		JsonResult jsonResult = new JsonResult();
		
		//暂时只支持获取全部
		
		switch (cacheName) {
		case "*":
			jsonResult.setData(GuavaCacheManager.getAllCacheStats());
			jsonResult.setMessage("成功获取了所有的cache!");
			break;

		default:
			break;
		}
		
		return jsonResult;
	}
	
	/**
	 * 清空缓存数据、并返回清空后的统计信息
	 * @param cacheName
	 * @return
	 */
	@RequestMapping(value = "/reset", method = RequestMethod.POST)
	public JsonResult cacheReset(String cacheName) {
		JsonResult jsonResult = new JsonResult();
		
		GuavaCacheManager.resetCache(cacheName);
		jsonResult.setMessage("已经成功重置了" + cacheName + "!");
	
		return jsonResult;
	}
	
	/**
	 * 返回所有的本地缓存统计信息
	 * @return
	 */
	@RequestMapping(value = "/stats/all", method = RequestMethod.POST)
	public JsonResult cacheStatsAll() {
		return cacheStats("*");
	}
	
	/**
	 * 分页查询数据详情
	 * @param pageSize
	 * @param pageNo
	 * @param cacheName
	 * @return
	 */
	@RequestMapping(value = "/queryDataByPage", method = RequestMethod.POST)
	public PageResult<Object> queryDataByPage(@RequestParam Map<String, String> params){
		int pageSize = Integer.valueOf(params.get("pageSize"));
		int pageNo = Integer.valueOf(params.get("pageNo"));
		String cacheName = params.get("cacheName");
		
		PageParams<Object> page = new PageParams<>();
		page.setPageSize(pageSize);
		page.setPageNo(pageNo);
		Map<String, Object> param = new HashMap<>();
		param.put("cacheName", cacheName);
		page.setParams(param);
		
		return GuavaCacheManager.queryDataByPage(page);
	}
}

Cache管理页面:Web页面,用于查看Cache列表、统计信息、数据,清空缓存,等(文件名:cache-admin.html)
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Cache Admin</title>
  5. <meta charset="UTF-8">
  6. <script src="./resources/js/jquery-2.1.4.js" charset="UTF-8" type="text/javascript"></script>
  7. <style>
  8. .important {color : red;}
  9. .attention {color : orange;}
  10. .perfect {color : green;}
  11. .highlight {color : blue;}
  12. table{border: 1px solid #8968CD; border-collapse: collapse;}
  13. th,td{border: 1px solid #8968CD; padding:6px;}
  14. td{color: green;}
  15. </style>
  16. </head>
  17. <body>
  18. <div>
  19. <div id="operations">
  20. Cache列表:
  21. <input type="button" value="刷新" onClick="refreshStatsAll();">
  22. <input type="checkbox" id="autoRefresh" onClick="toggleAutoRefreshStats();"><label for="autoRefresh">自动刷新(3s)</label>
  23. </div>
  24. <div><pre id="response" class="attention"></pre></div>
  25. <div><br><pre id="responseRawData" ></pre></div>
  26. </div>
  27. </body>
  28. <script>
  29. var autoRefreshInterval = 3000;
  30. var autoRefershObject;
  31. var requestStatsAll = {url : "/cache/admin/stats/all", params : "*", callback: requestStatsAllCallback};
  32. $(function() {
  33. refreshStatsAll();
  34. });
  35. function refreshStatsAll(){
  36. ajaxRequest(requestStatsAll.url, requestStatsAll.params, requestStatsAll.callback);
  37. }
  38. function sizeStatistics(obj){
  39. var c = "当前数据量/上限:" + obj.size + "/" + obj.maximumSize;
  40. c += "\n历史最高数据量:" + obj.highestSize;
  41. c += "\n最高数据量时间:" + obj.highestTime;
  42. return c;
  43. }
  44. function hitStatistics(obj){
  45. var c = "命中数量:" + obj.hitCount;
  46. c += "\n命中比例:" + obj.hitRate;
  47. c += "\n读库比例:" + obj.missRate;
  48. return c;
  49. }
  50. function loadStatistics(obj){
  51. var c = "成功加载数:" + obj.loadSuccessCount;
  52. c += "\n失败加载数:" + obj.loadExceptionCount;
  53. c += "\n总加载毫秒:" + obj.totalLoadTime;
  54. return c;
  55. }
  56. function requestStatsAllCallback(jsonResult){
  57. var html = "<table><tr><th>Cache名称</th> <th>数据量统计</th> <th>命中统计</th> <th>加载统计</th> <th>开始/重置时间</th> <th>操作</th> </tr>";
  58. $.each(jsonResult.data, function(idx, obj){
  59. html += "<tr><th>" + obj.cacheName + "</th>"
  60. + "<td>" + sizeStatistics(obj) + "</td>"
  61. + "<td>" + hitStatistics(obj) + "</td>"
  62. + "<td>" + loadStatistics(obj) + "</td>"
  63. + "<td>" + obj.resetTime +"\n\n失效时长:" + obj.survivalDuration + "(分钟)</td>"
  64. + "<td>"
  65. + "<a href='javascript:void(0)' onclick='resetCache(\""+obj.cacheName+"\");'>清空缓存</a>"
  66. + "\t<a href='javascript:void(0)' onclick='queryDataByPage(\""+obj.cacheName+"\");'>显示详情</a>"
  67. + "</td>"
  68. + "</tr>";
  69. });
  70. html += "</table>";
  71. $("#response").html(html);
  72. }
  73. function resetCache(cacheName){
  74. $.ajax({
  75. type : "POST",
  76. url : getRootPath()+"/cache/admin/reset",
  77. dataType : "json", //表示返回值类型
  78. data : {"cacheName":cacheName},
  79. success : function(jsonResult){alert(jsonResult.message);refreshStatsAll();}
  80. });
  81. }
  82. //定时刷新开关
  83. function toggleAutoRefreshStats(){
  84. if($("#autoRefresh").prop("checked")==true){
  85. autoRefershObject = setInterval(refreshStatsAll, autoRefreshInterval);
  86. }else{
  87. clearInterval(autoRefershObject);
  88. }
  89. }
  90. var pageParam = {pageNo : 1, pageSize : 10, cacheName : null};
  91. function resetpageParam(){
  92. pageParam.pageNo = 1;
  93. pageParam.totalPage = 0;
  94. }
  95. function queryDataByPage(cacheName){
  96. resetpageParam();
  97. pageParam.cacheName = cacheName;
  98. ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
  99. }
  100. function pageQueryCallback(jsonResult){
  101. pageParam.totalPage = jsonResult.totalPage;
  102. var html = "<label class='highlight'>Cache名称:" + pageParam.cacheName + "</label><br/><br/>";
  103. html += "<a href='javascript:void(0)' onclick='firstPage();'>首页 </a>\t";
  104. html += "<a href='javascript:void(0)' onclick='previousPage();'>上一页 </a>\t";
  105. html += "第<input type='number' id='pageNo' min='1' max='" + jsonResult.totalPage + "' value='" + jsonResult.pageNo + "' size='" + lengthOfNum(jsonResult.totalPage) + "' />页(共" + jsonResult.totalPage + "页)\t";
  106. html += "<a href='javascript:void(0)' onclick='nextPage();'>下一页 </a>\t";
  107. html += "<a href='javascript:void(0)' onclick='lastPage();'>末页</a>\t";
  108. html += "<br/><br/>";
  109. html += JSON.stringify(jsonResult.results[0], null, "\t");
  110. $("#responseRawData").html(html);
  111. $("#pageNo").blur(function(){
  112. pn = $("#pageNo").val();
  113. if(pn < 1){
  114. pn = 1;
  115. $("#pageNo").val(pn);
  116. }else if(pn > pageParam.totalPage){
  117. pn = pageParam.totalPage;
  118. $("#pageNo").val(pn);
  119. }
  120. pageParam.pageNo=pn;
  121. ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
  122. });
  123. //回车
  124. $("#pageNo").keyup(function(event){
  125. if(event.which != 13){
  126. return;
  127. }
  128. pn = $("#pageNo").val();
  129. if(pn < 1){
  130. pn = 1;
  131. $("#pageNo").val(pn);
  132. }else if(pn > pageParam.totalPage){
  133. pn = pageParam.totalPage;
  134. $("#pageNo").val(pn);
  135. }
  136. pageParam.pageNo=pn;
  137. ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
  138. });
  139. }
  140. function firstPage(){
  141. pageParam.pageNo=1;
  142. ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
  143. }
  144. function lastPage(){
  145. pageParam.pageNo=pageParam.totalPage;
  146. ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
  147. }
  148. function nextPage(){
  149. if(pageParam.pageNo==pageParam.totalPage){
  150. alert("已经是最后一页了!");
  151. return;
  152. }
  153. pageParam.pageNo++;
  154. ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
  155. }
  156. function previousPage(){
  157. if(pageParam.pageNo==1){
  158. alert("已经是第一页了!");
  159. return;
  160. }
  161. pageParam.pageNo--;
  162. ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
  163. }
  164. //js获取项目根路径,如: http://localhost:8083/uimcardprj
  165. function getRootPath() {
  166. //获取当前网址,如: http://localhost:8083/uimcardprj/share/meun.jsp
  167. var curWwwPath = window.document.location.href;
  168. //获取主机地址之后的目录,如: uimcardprj/share/meun.jsp
  169. var pathName = window.document.location.pathname;
  170. var pos = curWwwPath.indexOf(pathName);
  171. //获取主机地址,如: http://localhost:8083
  172. var localhostPath = curWwwPath.substring(0, pos);
  173. //获取带"/"的项目名,如:/uimcardprj
  174. var projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
  175. return (localhostPath + projectName);
  176. }
  177. //发送ajax请求
  178. function ajaxRequest(url, params, successCallback, contentType, errorCallback, async) {
  179. var _async = async || true;
  180. $.ajax({
  181. type : "POST",
  182. url : getRootPath() + url,
  183. async : _async,
  184. contentType : contentType,
  185. dataType : "json", //表示返回值类型
  186. data : params,
  187. success : successCallback,
  188. error : errorCallback
  189. });
  190. }
  191. function lengthOfNum(num){
  192. var length = 1;
  193. var _num = num;
  194. while((_num=_num/10) >= 1){
  195. length++;
  196. }
  197. return length;
  198. }
  199. </script>
  200. </html>
<!DOCTYPE html>
<html>
  <head>
    <title>Cache Admin</title>
	
    <meta charset="UTF-8">
	
    <script src="./resources/js/jquery-2.1.4.js" charset="UTF-8" type="text/javascript"></script>

	<style>
		.important {color : red;}
		.attention {color : orange;}
		.perfect {color : green;}
		.highlight {color : blue;}
		table{border: 1px solid #8968CD; border-collapse: collapse;}
		th,td{border: 1px solid #8968CD; padding:6px;}
		td{color: green;}
	</style>

  </head>
  
  <body>
  
  	<div>
		<div id="operations">
			Cache列表: 
			<input type="button" value="刷新" onClick="refreshStatsAll();"> 
			<input type="checkbox" id="autoRefresh" onClick="toggleAutoRefreshStats();"><label for="autoRefresh">自动刷新(3s)</label>
			
		</div>
		<div><pre id="response" class="attention"></pre></div>
		<div><br><pre id="responseRawData" ></pre></div>
	</div>
  
  </body>

<script>
	var autoRefreshInterval = 3000;
	var autoRefershObject;
	var requestStatsAll = {url : "/cache/admin/stats/all", params : "*", callback: requestStatsAllCallback};

	$(function() {
		refreshStatsAll();
	});

	function refreshStatsAll(){
		ajaxRequest(requestStatsAll.url, requestStatsAll.params, requestStatsAll.callback);
	}
	
	function sizeStatistics(obj){
		var c = "当前数据量/上限:" + obj.size + "/" + obj.maximumSize;
		 c += "\n历史最高数据量:" + obj.highestSize;
		 c += "\n最高数据量时间:" + obj.highestTime;
				
		return c;
	}
	
	function hitStatistics(obj){
		var c = "命中数量:" + obj.hitCount;
		c += "\n命中比例:" + obj.hitRate;
		c += "\n读库比例:" + obj.missRate;
				
		return c;
	}
	
	function loadStatistics(obj){
		var c = "成功加载数:" + obj.loadSuccessCount;
		c += "\n失败加载数:" + obj.loadExceptionCount;
		c += "\n总加载毫秒:" + obj.totalLoadTime;
		
		return c;
	}

	function requestStatsAllCallback(jsonResult){
		var html = "<table><tr><th>Cache名称</th> <th>数据量统计</th> <th>命中统计</th> <th>加载统计</th> <th>开始/重置时间</th> <th>操作</th> </tr>";
		$.each(jsonResult.data, function(idx, obj){
			html += "<tr><th>" + obj.cacheName + "</th>"
				+ "<td>" + sizeStatistics(obj) + "</td>"
				+ "<td>" + hitStatistics(obj) + "</td>"
				+ "<td>" + loadStatistics(obj) + "</td>"
				+ "<td>" + obj.resetTime +"\n\n失效时长:" + obj.survivalDuration + "(分钟)</td>"
				+ "<td>"
				+ "<a href='javascript:void(0)' onclick='resetCache(\""+obj.cacheName+"\");'>清空缓存</a>"
				+ "\t<a href='javascript:void(0)' onclick='queryDataByPage(\""+obj.cacheName+"\");'>显示详情</a>"
				+ "</td>"
				+ "</tr>";
		});
		html += "</table>";
		$("#response").html(html);
	}

	function resetCache(cacheName){
		$.ajax({
			type : "POST",
			url : getRootPath()+"/cache/admin/reset",
			dataType : "json", //表示返回值类型
			data : {"cacheName":cacheName},
			success : function(jsonResult){alert(jsonResult.message);refreshStatsAll();}
		});
	}
	
	//定时刷新开关
	function toggleAutoRefreshStats(){
		if($("#autoRefresh").prop("checked")==true){
			autoRefershObject = setInterval(refreshStatsAll, autoRefreshInterval);
		}else{
			clearInterval(autoRefershObject);
		}
	}
	
	var pageParam = {pageNo : 1, pageSize : 10, cacheName : null};
	
	function resetpageParam(){
		pageParam.pageNo = 1;
		pageParam.totalPage = 0;
	}
	
	function queryDataByPage(cacheName){
		resetpageParam();
		pageParam.cacheName = cacheName;
		ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
	}
	
	function pageQueryCallback(jsonResult){
		pageParam.totalPage = jsonResult.totalPage;
		var html = "<label class='highlight'>Cache名称:" + pageParam.cacheName + "</label><br/><br/>";
		html += "<a href='javascript:void(0)' onclick='firstPage();'>首页 </a>\t";
		html += "<a href='javascript:void(0)' onclick='previousPage();'>上一页 </a>\t";
		html += "第<input type='number' id='pageNo' min='1' max='" + jsonResult.totalPage + "' value='" + jsonResult.pageNo + "' size='" + lengthOfNum(jsonResult.totalPage) + "' />页(共" + jsonResult.totalPage + "页)\t";
		html += "<a href='javascript:void(0)' onclick='nextPage();'>下一页 </a>\t";
		html += "<a href='javascript:void(0)' onclick='lastPage();'>末页</a>\t";
		html += "<br/><br/>";
		html += JSON.stringify(jsonResult.results[0], null, "\t");
		$("#responseRawData").html(html);
		
		$("#pageNo").blur(function(){
			pn = $("#pageNo").val();
			if(pn < 1){
				pn = 1;
				$("#pageNo").val(pn);
			}else if(pn > pageParam.totalPage){
				pn = pageParam.totalPage;
				$("#pageNo").val(pn);
			}
			
			pageParam.pageNo=pn;
			ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
		});
		
		//回车
		$("#pageNo").keyup(function(event){
			if(event.which != 13){
				return;
			}
			
			pn = $("#pageNo").val();
			if(pn < 1){
				pn = 1;
				$("#pageNo").val(pn);
			}else if(pn > pageParam.totalPage){
				pn = pageParam.totalPage;
				$("#pageNo").val(pn);
			}
			
			pageParam.pageNo=pn;
			ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
		});
	}
	
	function firstPage(){
		pageParam.pageNo=1;
		ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
	}
	
	function lastPage(){
		pageParam.pageNo=pageParam.totalPage;
		ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
	}
	
	function nextPage(){
		if(pageParam.pageNo==pageParam.totalPage){
			alert("已经是最后一页了!");
			return;
		}
		pageParam.pageNo++;
		ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
	}
	
	function previousPage(){
		if(pageParam.pageNo==1){
			alert("已经是第一页了!");
			return;
		}
		pageParam.pageNo--;
		ajaxRequest("/cache/admin/queryDataByPage", pageParam, pageQueryCallback);
	}

	//js获取项目根路径,如: http://localhost:8083/uimcardprj
	function getRootPath() {
		//获取当前网址,如: http://localhost:8083/uimcardprj/share/meun.jsp
		var curWwwPath = window.document.location.href;
		//获取主机地址之后的目录,如: uimcardprj/share/meun.jsp
		var pathName = window.document.location.pathname;
		var pos = curWwwPath.indexOf(pathName);
		//获取主机地址,如: http://localhost:8083
		var localhostPath = curWwwPath.substring(0, pos);
		//获取带"/"的项目名,如:/uimcardprj
		var projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
		return (localhostPath + projectName);
	}
		
	//发送ajax请求
	function ajaxRequest(url, params, successCallback, contentType,	errorCallback, async) {
		var _async = async || true;

		$.ajax({
			type : "POST",
			url : getRootPath() + url,
			async : _async,
			contentType : contentType,
			dataType : "json", //表示返回值类型
			data : params,
			success : successCallback,
			error : errorCallback
		});
	}
	
	function lengthOfNum(num){
		var length = 1;
		var _num = num;
		while((_num=_num/10) >= 1){
			length++;
		}
		return length;
	}
</script>

</html>


其他代码,包括测试页面和测试Controller请参考源代码:https://github.com/xujijun/MyJavaStudio,有问题请留言。^_^

测试页面:


管理页面:



(原创文章,转载请注明转自Clement-Xu的博客:http://blog.youkuaiyun.com/clementad/article/details/46491701,源码地址:https://github.com/xujijun/MyJavaStudio

<think>好的,我需要回答用户的问题:“Caffeine 是否是对 Spring Cache封装实现”。首先,我应该回顾Spring Cache的基本概念,然后了解Caffeine在其中的角色。 根据引用[2]提到的,Spring Cache一个抽象层,支持多种缓存提供者,比如EhCache、Redis、Caffeine等。这说明Caffeine是作为Spring Cache一个实现,而不是封装Spring Cache。 接下来,引用[1][3]都提到Spring Cache整合了不同的缓存技术,比如用Caffeine作为本地缓存,Redis作为远程缓存。这说明Caffeine在这里是被Spring Cache管理封装,而不是反过来。 因此,正确的理解应该是:Caffeine是Spring Cache支持的缓存实现之一,通过Spring Cache的接口(如CacheManagerCache)进行集成。用户在使用时,通过配置选择Caffeine作为具体的缓存提供者,而Spring Cache负责统一管理这些缓存操作。所以,Caffeine并不是对Spring Cache封装,而是Spring Cache封装了Caffeine作为其实现的一部分。</think>Caffeine **不是** 对 Spring Cache封装实现,而是 **Spring Cache 整合的缓存实现之一**。两者的关系可以理解为: - **Spring Cache** 是缓存抽象层,定义了统一的接口(如 `Cache` `CacheManager`),用于解耦业务代码与具体缓存技术[^2]。 - **Caffeine** 是一个高性能的本地缓存库,Spring Cache 通过适配器模式将其封装为符合 `Cache` 接口的实现类(如 `CaffeineCache`),从而集成到 Spring 生态中[^1][^3]。 ### 具体说明 1. **Spring Cache 的定位** Spring Cache 提供标准化的缓存操作注解(如 `@Cacheable`)接口,开发者无需关心底层是 Caffeine、Redis 还是其他缓存技术[^2]。例如: ```java @Cacheable(value = "users", key = "#id") public User getUserById(Long id) { ... } ``` 2. **Caffeine 的角色** Caffeine 作为本地缓存的具体实现,需通过 Spring Cache 的配置类(如 `CaffeineCacheManager`)接入。例如,在配置中指定 Caffeine 的参数: ```java @Bean public CacheManager cacheManager() { CaffeineCacheManager manager = new CaffeineCacheManager(); manager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES)); return manager; } ``` 此时,Spring Cache 会代理 Caffeine 的缓存操作,实现透明化调用。 ### 对比关系 | 角色 | 作用 | 与对方的关系 | |---------------------|--------------------------------|-------------------------------| | Spring Cache | 定义缓存标准接口与注解 | 整合并管理 Caffeine 等实现 | | Caffeine | 提供本地缓存的高效读写能力 | 作为 Spring Cache 的底层实现 | ### 相关问题 1. **如何配置 Spring Cache 使用 Caffeine?** 2. **Caffeine 与其他本地缓存(如 Guava)在 Spring 中的性能差异?** 3. **Spring Cache 如何同时管理本地缓存(Caffeine)分布式缓存(Redis)?** : Spring Cache 支持多级缓存配置,例如结合 Caffeine Redis 实现性能优化。 [^2]: Spring Cache 的核心价值在于标准化缓存操作,降低对具体实现的依赖。 : Caffeine 的配置需通过 Spring Cache 的 `CacheManager` 接口适配。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值