我们先看看运行效果图:
上图中,我们启动了两个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。