我们先看看运行效果图:

上图中,我们启动了两个java进程,分别运行在 8080 和 8081 端口。当我们在 8080 端口添加 name 和 version 缓存后,立马在 8081 端口所在的 Java 进程中显示出来。同样,从 8081 端口删除一个缓存,立马在 8080 中体现出来。如果你仔细看,你会发现我们在 8080 端口中新添加的 name 和 version 缓存信息后,在 8081 中首次显示出来的 level 为 2(表示来自二级缓存),然后变为 level = 1(一级缓存)。
我们先看看项目结构,如下图:

下面将配置 Freemarker 模板,如下:
## Freemarker
# 指定 HttpServletRequest 的属性是否可以覆盖 controller 中与 model 同名项
spring.freemarker.allow-request-override=false
# 是否开启模版缓存
spring.freemarker.cache=true
# 是否检查模版路径是否存在
spring.freemarker.check-template-location=true
# 设定模版编码
spring.freemarker.charset=utf-8
# 设定Content-Type
spring.freemarker.content-type=text/html
# 设定所有request的属性在merge到模板的时候,是否要都添加到model中
spring.freemarker.expose-request-attributes=false
# 设定所有HttpSession的属性在merge到模板的时候,是否要都添加到model中
spring.freemarker.expose-session-attributes=false
# 设定是否以springMacroRequestContext的形式暴露RequestContext给Spring’s macrolibrary使用
spring.freemarker.expose-spring-macro-helpers=false
# 设定freemarker模板的前缀
spring.freemarker.prefix=
# 指定RequestContext属性的名
spring.freemarker.request-context-attribute=freemarker
# 设定模板的后缀
spring.freemarker.suffix=.ftl
# 设定模板的加载路径,多个以逗号分隔,默认: ["classpath:/templates/"]
spring.freemarker.template-loader-path=classpath:/templates/#J2Cache configuration
#########################################
# Cache Broadcast Method
# values:
# jgroups -> use jgroups's multicast
# redis -> use redis publish/subscribe mechanism (using jedis)
# lettuce -> use redis publish/subscribe mechanism (using lettuce, Recommend)
# rabbitmq -> use RabbitMQ publisher/consumer mechanism
# rocketmq -> use RocketMQ publisher/consumer mechanism
# none -> don't notify the other nodes in cluster
# xx.xxxx.xxxx.Xxxxx your own cache broadcast policy classname that implement net.oschina.j2cache.cluster.ClusterPolicy
#########################################
j2cache.broadcast = redis
# jgroups properties
jgroups.channel.name = j2cache
jgroups.configXml = /network.xml
# RabbitMQ properties
rabbitmq.exchange = j2cache
rabbitmq.host = localhost
rabbitmq.port = 5672
rabbitmq.username = guest
rabbitmq.password = guest
# RocketMQ properties
rocketmq.name = j2cache
rocketmq.topic = j2cache
# use ; to split multi hosts
rocketmq.hosts = 127.0.0.1:9876
#########################################
# Level 1&2 provider
# values:
# none -> disable this level cache
# ehcache -> use ehcache2 as level 1 cache
# ehcache3 -> use ehcache3 as level 1 cache
# caffeine -> use caffeine as level 1 cache(only in memory)
# redis -> use redis as level 2 cache (using jedis)
# lettuce -> use redis as level 2 cache (using lettuce)
# readonly-redis -> use redis as level 2 cache ,but never write data to it. if use this provider, you must uncomment `j2cache.L2.config_section` to make the redis configurations available.
# memcached -> use memcached as level 2 cache (xmemcached),
# [classname] -> use custom provider
#########################################
j2cache.L1.provider_class = ehcache3
j2cache.L2.provider_class = redis
# When L2 provider isn't `redis`, using `L2.config_section = redis` to read redis configurations
# j2cache.L2.config_section = redis
# Enable/Disable ttl in redis cache data (if disabled, the object in redis will never expire, default:true)
# NOTICE: redis hash mode (redis.storage = hash) do not support this feature)
j2cache.sync_ttl_to_redis = true
# Whether to cache null objects by default (default false)
j2cache.default_cache_null_object = true
#########################################
# Cache Serialization Provider
# values:
# fst -> using fast-serialization (recommend)
# kryo -> using kryo serialization
# json -> using fst's json serialization (testing)
# fastjson -> using fastjson serialization (embed non-static class not support)
# java -> java standard
# fse -> using fse serialization
# [classname implements Serializer]
#########################################
j2cache.serialization = json
#json.map.person = net.oschina.j2cache.demo.Person
#########################################
# Ehcache configuration
#########################################
# ehcache.configXml = /ehcache.xml
ehcache3.configXml = /ehcache3.xml
ehcache3.defaultHeapSize = 10000
#########################################
# Caffeine configuration
# caffeine.region.[name] = size, xxxx[s|m|h|d]
#
#########################################
caffeine.properties = /caffeine.properties
#########################################
# Redis connection configuration
#########################################
#########################################
# Redis Cluster Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# cluster -> cluster servers (\u6570\u636e\u5e93\u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528 database = 0\uff09
# sharded -> sharded servers (\u5bc6\u7801\u3001\u6570\u636e\u5e93\u5fc5\u987b\u5728 hosts \u4e2d\u6307\u5b9a\uff0c\u4e14\u8fde\u63a5\u6c60\u914d\u7f6e\u65e0\u6548 ; redis://user:password@127.0.0.1:6379/0\uff09
#
#########################################
redis.mode = single
#redis storage mode (generic|hash)
redis.storage = generic
## redis pub/sub channel name
redis.channel = j2cache
## redis pub/sub server (using redis.hosts when empty)
redis.channel.host =
#cluster name just for sharded
redis.cluster_name = j2cache
## redis cache namespace optional, default[empty]
redis.namespace =
## redis command scan parameter count, default[1000]
#redis.scanCount = 1000
## connection
# Separate multiple redis nodes with commas, such as 192.168.0.10:6379,192.168.0.11:6379,192.168.0.12:6379
redis.hosts = 127.0.0.1:6379
redis.timeout = 2000
redis.password =
redis.database = 0
redis.ssl = false
## redis pool properties
redis.maxTotal = 100
redis.maxIdle = 10
redis.maxWaitMillis = 5000
redis.minEvictableIdleTimeMillis = 60000
redis.minIdle = 1
redis.numTestsPerEvictionRun = 10
redis.lifo = false
redis.softMinEvictableIdleTimeMillis = 10
redis.testOnBorrow = true
redis.testOnReturn = false
redis.testWhileIdle = true
redis.timeBetweenEvictionRunsMillis = 300000
redis.blockWhenExhausted = false
redis.jmxEnabled = false
#########################################
# Lettuce scheme
#
# redis -> single redis server
# rediss -> single redis server with ssl
# redis-sentinel -> redis sentinel
# redis-cluster -> cluster servers
#
#########################################
## redis command scan parameter count, default[1000]
#lettuce.scanCount = 1000
lettuce.namespace =
lettuce.storage = hash
lettuce.channel = j2cache
lettuce.scheme = redis
lettuce.hosts = 127.0.0.1:6379
lettuce.password =
lettuce.database = 0
lettuce.sentinelMasterId =
lettuce.maxTotal = 100
lettuce.maxIdle = 10
lettuce.minIdle = 10
# timeout in milliseconds
lettuce.timeout = 10000
# redis cluster topology refresh interval in milliseconds
lettuce.clusterTopologyRefresh = 3000
#########################################
# memcached server configurations
# refer to https://gitee.com/mirrors/XMemcached
#########################################
memcached.servers = 127.0.0.1:11211
memcached.username =
memcached.password =
memcached.connectionPoolSize = 10
memcached.connectTimeout = 1000
memcached.failureMode = false
memcached.healSessionInterval = 1000
memcached.maxQueuedNoReplyOperations = 100
memcached.opTimeout = 100
memcached.sanitizeKeys = false<!-- for ehcache 3.x -->
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">
<!-- Don't remote default cache configuration -->
<cache-template name="default">
<key-type>java.lang.String</key-type>
<value-type>java.io.Serializable</value-type>
<expiry>
<ttl unit="seconds">1800</ttl>
</expiry>
<resources>
<heap>1000</heap>
<offheap unit="MB">100</offheap>
</resources>
</cache-template>
<cache alias="default" uses-template="default"/>
</config>本文件提供 UI 简单界面,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>J2Cache</title>
<script type="text/javascript" src="js/jquery-v1.8.1.js"></script>
</head>
<body>
<h3>新增/修改缓存</h3>
<div>
键: <input id="key" type="text"/>
值: <input id="value" type="text"/>
<button id="btnSubmit">提交</button>
</div>
<h3>缓存列表
<label style="font-size:14px;font-weight:bold;">
<input type="checkbox" id="autoRefresh" value="1" checked>自动刷新
</label>
</h3>
<table width="100%" border="1">
<thead>
<tr>
<th>key</th>
<th>value</th>
<th>region</th>
<th>level</th>
<th></th>
</tr>
</thead>
<tbody id="keyList"></tbody>
</table>
<script type="text/javascript">
var autoRefresh = false;
var intervalObj = null;
$(function(){
autoRefresh = $("#autoRefresh:checked").val();
function getCacheList(){
$.getJSON("/getCacheList", {}, function(ret){
var htmlCode = [];
for(var attr in ret) {
var row = ret[attr] || {};
htmlCode.push("<tr>")
htmlCode.push("<td>" + attr + "</td>");
htmlCode.push("<td>" + row.value + "</td>");
htmlCode.push("<td>" + row.region + "</td>");
htmlCode.push("<td>" + row.level + "</td>");
htmlCode.push("<td><a href=\"javascript:void(0);\" key=\"" + attr + "\"" +
" onclick=\"J2CacheDemo.deleteCache(event)\">删除</a></td>");
htmlCode.push("</tr>")
}
$("#keyList").html(htmlCode.join(""));
});
}
function startInterval() {
if(!!intervalObj) {
clearInterval(intervalObj);
}
intervalObj = setInterval(function(){
if(!!autoRefresh) {
getCacheList();
}
}, 2000);
}
$("#autoRefresh").change(function(){
autoRefresh = $("#autoRefresh:checked").val();
startInterval();
});
// 添加缓存
$("#btnSubmit").click(function(){
var key = $("#key").val();
var value = $("#value").val();
$.getJSON("/addCache", {
key: key,
value: value
}, function(ret){
if(ret.statu == "1") {
$("#key").val("");
$("#value").val("");
}
alert(ret.message);
});
});
function deleteCache(event) {
var key = $(event.target).attr("key");
$.getJSON("/deleteCache", {
key: key
}, function(ret){
if(ret.statu == "1") {
getCacheList();
}
alert(ret.message);
})
}
getCacheList();
startInterval();
window.J2CacheDemo = {
deleteCache: deleteCache
};
});
</script>
</body>
</html>package com.huangx.j2cache.demo2;
import net.oschina.j2cache.CacheChannel;
import net.oschina.j2cache.CacheObject;
import net.oschina.j2cache.J2Cache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* 可以通过运行时动态指定端口,如下:
* --server.port=8081
*/
@Controller
@SpringBootApplication
public class J2cacheDemo2Application {
private static final String REGION_NAME = "default";
private CacheChannel cacheChannel;
public J2cacheDemo2Application() {
this.cacheChannel = J2Cache.getChannel();
}
public static void main(String[] args) {
SpringApplication.run(J2cacheDemo2Application.class, args);
}
@RequestMapping("/")
public String index() {
return "index";
}
/**
* 添加缓存
* @param key 键
* @param value 值
* @return
*/
@GetMapping("/addCache")
@ResponseBody
public Map<String,Object> addCache(@RequestParam("key") String key,
@RequestParam("value") String value) {
Map<String,Object> map = new HashMap<>();
try {
this.cacheChannel.set(REGION_NAME, key, value);
map.put("statu", "1");
map.put("message", "操作成功");
} catch (Exception e) {
e.printStackTrace();
map.put("statu", "0");
map.put("message", "新增缓存失败,原因:" + e.getMessage());
}
return map;
}
/**
* 删除缓存
* @param key 键
* @return
*/
@GetMapping("/deleteCache")
@ResponseBody
public Map<String,Object> deleteCache(@RequestParam("key") String key) {
Map<String,Object> map = new HashMap<>();
try {
this.cacheChannel.evict(REGION_NAME, key);
map.put("statu", "1");
map.put("message", "操作成功");
} catch (Exception e) {
e.printStackTrace();
map.put("statu", "0");
map.put("message", "删除缓存失败,原因:" + e.getMessage());
}
return map;
}
/**
* 获取缓存列表
* @return
*/
@GetMapping("/getCacheList")
@ResponseBody
public Map<String, CacheObject> getCacheList() {
Map<String, CacheObject> map = new HashMap<String, CacheObject>();
try {
map = this.cacheChannel.get(REGION_NAME, this.cacheChannel.keys(REGION_NAME));
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}运行上面实例,在程序参数中通过 --server.port 指定服务的端口,分别运行 --server.port=8080 和 --server.port=8081。