非常非常感谢http://blog.csdn.net/clementad/article/details/46491701
由于项目的实际情况,需要缓存一些比较不经常改动的数据在本地服务器中,以提高接口处理的速度。决定采用Guava Cache之后,整理了一些具体需求:
- 由于要缓存的key-value对比较多,需要一个封装好的类被继承,子类可以简单的实现把key-value缓存到Guava Cache中;
- 需要定义一个接口,简单的定义一个get(K key)方法,方便使用;
- 需要有一个管理界面,统计缓存的命中率、记录数,以便以后做出相应的调整;
- 需要有一个管理界面,重设、清空缓存中的数据,或使缓存中的数据失效,以强行让服务器重新从数据库获取数据,并记录重置的时间;
- 需要有一个管理界面,分页查看缓存中的具体内容。
现在,该系统已经实现,并已经在正式环境中运行了一段时间,日均总命中次数超过一百万,大部分缓存的命中率在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种角色和代码的对应关系如下:
- Context:Service.java,(策略的使用者)
- Strategy:ILocalCache.java,(定义策略的接口)
- ConcreteStrategy:LCAreaIdToArea.java,其他ILocalCache实现类 ……,(实现策略的类)
- 辅助类:GuavaAbstractLoadingCache.java,(实现策略的辅助类,就是封装了Guava Cache的一个类)
各角色对应的具体功能如下:
- Service:cache的使用者
- Cache接口:定义一个get()方法,通过key获取value
- Cache实现类:利用辅助类,实现Cache接口的get()方法
- Guava Cache实现辅助类:封装了对Guava Cache的利用,包括cache的创建、从数据源获取数据、定义过时策略、等
除了上面的核心功能模块,其他辅助的功能模块如下:
Cache管理类:封装了对所有实现类进行管理的一些方法,包括清空Cache中的数据、查看数据、查看统计信息、等
Cache Controller:调用Cache管理类,为管理页面提供Web HTTP接口
Cache管理页面:Web页面,用于查看Cache列表、统计信息、数据,清空缓存,等
各模块的具体代码,代码中已经包括了比较详尽的注解:
主要的依赖包:
[html] view plain copy
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <version>18.0</version>
- </dependency>
Cache接口:定义一个get()方法,通过key获取value
[java] view plain copy
-
-
-
-
-
-
-
- public interface ILocalCache <K, V> {
-
-
-
-
-
-
- public V get(K key);
- }
Guava Cache实现辅助类:封装了对Guava Cache的利用,包括cache的创建、从数据源获取数据、定义过时策略、等
[java] view plain copy
- package com.xjj.cache.guava;
-
-
-
-
-
-
-
-
-
-
-
-
- public abstract class GuavaAbstractLoadingCache <K, V> {
- protected final Logger logger = LoggerFactory.getLogger(this.getClass());
-
-
- private int maximumSize = 1000;
- private int expireAfterWriteDuration = 60;
- private TimeUnit timeUnit = TimeUnit.MINUTES;
-
- private Date resetTime;
- private long highestSize=0;
- private Date highestTime;
-
- private LoadingCache<K, V> cache;
-
-
-
-
-
- public LoadingCache<K, V> getCache() {
- if(cache == null){
- synchronized (this) {
- if(cache == null){
- cache = CacheBuilder.newBuilder().maximumSize(maximumSize)
- .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;
- }
-
-
-
-
-
-
- protected abstract V fetchData(K key);
-
-
-
-
-
-
-
- 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;
- }
-
-
-
-
-
- public void setMaximumSize(int maximumSize) {
- this.maximumSize = maximumSize;
- }
-
-
-
-
-
- public void setExpireAfterWriteDuration(int expireAfterWriteDuration) {
- this.expireAfterWriteDuration = expireAfterWriteDuration;
- }
- }
Cache实现类:利用辅助类,实现Cache接口的get()方法,(以一个areaId -> Area为例子)
[java] view plain copy
- 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);
- }
-
-
- }
-
- package com.xjj.cache.local.impl;
-
-
-
-
-
-
- @Component
- public class LCAreaIdToArea extends GuavaAbstractLoadingCache<Integer, Area> implements ILocalCache<Integer, Area> {
-
-
-
-
-
- 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);
-
-
- 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的使用者
[java] view plain copy
-
-
-
-
-
- @Service
- public class AreaService implements IAreaService {
- @Resource(name="LCAreaIdToArea")
- ILocalCache<Integer, Area> lCAreaIdToArea;
-
-
-
-
-
-
- @Override
- public Area getAreaById(int areaId) {
- return lCAreaIdToArea.get(areaId);
- }
-
- }
Cache管理类:封装了对所有实现类进行管理的一些方法,包括清空Cache中的数据、查看数据、查看统计信息、等
代码中所涉及到的其他类(SpringContextUtil、PageParams、PageResult)请参考源代码:https://github.com/xujijun/MyJavaStudio
[java] view plain copy
- package com.xjj.cache.guava;
-
-
-
-
-
-
- public class GuavaCacheManager {
-
- private static Map<String, ? extends GuavaAbstractLoadingCache<Object, Object>> cacheNameToObjectMap = null;
-
-
-
-
-
-
- @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;
-
- }
-
-
-
-
-
-
- private static GuavaAbstractLoadingCache<Object, Object> getCacheByName(String cacheName){
- return (GuavaAbstractLoadingCache<Object, Object>) getCacheMap().get(cacheName);
- }
-
-
-
-
-
- public static Set<String> getCacheNames() {
- return getCacheMap().keySet();
- }
-
-
-
-
-
- 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;
- }
-
-
-
-
-
-
- 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);
- 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;
- }
-
-
-
-
-
- public static void resetCache(String cacheName){
- GuavaAbstractLoadingCache<Object, Object> cache = getCacheByName(cacheName);
- cache.getCache().invalidateAll();
- cache.setResetTime(new Date());
- }
-
-
-
-
-
-
- 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接口
[java] view plain copy
- package com.xjj.web.controller;
-
-
-
-
-
-
- @RestController
- @RequestMapping("/cache/admin")
- public class CacheAdminController {
-
-
-
-
-
-
- @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;
- }
-
-
-
-
-
-
- @RequestMapping(value = "/reset", method = RequestMethod.POST)
- public JsonResult cacheReset(String cacheName) {
- JsonResult jsonResult = new JsonResult();
-
- GuavaCacheManager.resetCache(cacheName);
- jsonResult.setMessage("已经成功重置了" + cacheName + "!");
-
- return jsonResult;
- }
-
-
-
-
-
- @RequestMapping(value = "/stats/all", method = RequestMethod.POST)
- public JsonResult cacheStatsAll() {
- return cacheStats("*");
- }
-
-
-
-
-
-
-
-
- @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)
[html] view plain copy
- <!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.csdn.net/clementad/article/details/46491701,源码地址:https://github.com/xujijun/MyJavaStudio)