GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix: Groovy 脚本缓存增加跨实例失效同步与可插拔缓存策略 (17.x/springboot-starter-script) · codingapi/springboot-framework@0355e56

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • springboot-starter-script/src
    • main/java/com/codingapi/springboot/script
      • GroovyScript.java
      • cache
        • DefaultGroovyScriptCache.java
        • GroovyScriptCache.java
        • GroovyScriptCacheContext.java
        • GroovyScriptCacheEvent.java
        • GroovyScriptCacheSync.java
        • GroovyScriptCacheSyncContext.java
        • LocalGroovyScriptCacheSync.java
    • test/java/com/codingapi/springboot/script/cache
      • GroovyScriptCacheEventBridgeTest.java
      • GroovyScriptCacheSyncContextTest.java
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎springboot-starter-script/src/main/java/com/codingapi/springboot/script/GroovyScript.java‎

Copy file name to clipboardExpand all lines: springboot-starter-script/src/main/java/com/codingapi/springboot/script/GroovyScript.java
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.codingapi.springboot.script;
22

3+
import com.codingapi.springboot.framework.event.EventPusher;
4+
import com.codingapi.springboot.script.cache.GroovyScriptCacheEvent;
5+
import com.codingapi.springboot.script.cache.GroovyScriptCacheSyncContext;
36
import com.codingapi.springboot.script.temp.TempGroovyScriptContext;
47
import com.codingapi.springboot.script.meta.GroovyMetadata;
58
import com.codingapi.springboot.script.repository.GroovyScriptRepositoryContext;
@@ -161,21 +164,33 @@ public void temp() {
161164

162165
/**
163166
* 保存对象
167+
* 保存后发布缓存变更事件,其他实例收到事件后失效本地缓存
164168
*/
165169
public void save() {
166170
this.updateTime = System.currentTimeMillis();
167171
GroovyScriptRepositoryContext.getInstance().save(this);
168172
// 清理临时数据
169173
TempGroovyScriptContext.getInstance().remove(this.getKey());
174+
// 发布脚本缓存变更事件:跨进程广播由 SPI 实现,进程内监听由框架事件分发
175+
GroovyScriptCacheEvent event = GroovyScriptCacheEvent.save(this.getKey(),
176+
GroovyScriptCacheSyncContext.getInstance().getId(), this.updateTime);
177+
GroovyScriptCacheSyncContext.getInstance().publish(event);
178+
EventPusher.push(event);
170179
}
171180

172181
/**
173182
* 删除对象
183+
* 删除后发布缓存变更事件,其他实例收到事件后失效本地缓存
174184
*/
175185
public void remove() {
176186
GroovyScriptRepositoryContext.getInstance().delete(this.getKey());
177187
// 清理临时数据
178188
TempGroovyScriptContext.getInstance().remove(this.getKey());
189+
// 发布脚本缓存变更事件:跨进程广播由 SPI 实现,进程内监听由框架事件分发
190+
GroovyScriptCacheEvent event = GroovyScriptCacheEvent.remove(this.getKey(),
191+
GroovyScriptCacheSyncContext.getInstance().getId());
192+
GroovyScriptCacheSyncContext.getInstance().publish(event);
193+
EventPusher.push(event);
179194
}
180195

181196
/**
Collapse file

‎springboot-starter-script/src/main/java/com/codingapi/springboot/script/cache/DefaultGroovyScriptCache.java‎

Copy file name to clipboard
+54Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.codingapi.springboot.script.cache;
2+
3+
import com.codingapi.springboot.script.GroovyScript;
4+
5+
import java.util.LinkedHashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
/**
10+
* 默认脚本缓存容器实现:进程内 LRU 缓存
11+
*/
12+
public class DefaultGroovyScriptCache implements GroovyScriptCache {
13+
14+
private final static int MAX_CACHE_SIZE = 10 * 1024;
15+
16+
private final Map<String, GroovyScript> cache = new LinkedHashMap<>(16, 0.75f, true) {
17+
@Override
18+
protected boolean removeEldestEntry(Map.Entry<String, GroovyScript> eldest) {
19+
return size() > MAX_CACHE_SIZE;
20+
}
21+
};
22+
23+
@Override
24+
public void save(GroovyScript script) {
25+
if (script != null) {
26+
this.cache.put(script.getKey(), script);
27+
}
28+
}
29+
30+
@Override
31+
public GroovyScript get(String key) {
32+
return this.cache.get(key);
33+
}
34+
35+
@Override
36+
public void remove(String key) {
37+
this.cache.remove(key);
38+
}
39+
40+
@Override
41+
public List<String> keys() {
42+
return this.cache.keySet().stream().toList();
43+
}
44+
45+
@Override
46+
public int count() {
47+
return this.cache.size();
48+
}
49+
50+
@Override
51+
public void clear() {
52+
this.cache.clear();
53+
}
54+
}
Collapse file

‎springboot-starter-script/src/main/java/com/codingapi/springboot/script/cache/GroovyScriptCache.java‎

Copy file name to clipboard
+54Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.codingapi.springboot.script.cache;
2+
3+
import com.codingapi.springboot.script.GroovyScript;
4+
5+
import java.util.List;
6+
7+
/**
8+
* 脚本缓存容器接口
9+
* 默认实现为进程内 LRU 缓存({@link DefaultGroovyScriptCache}),
10+
* 多实例部署时可替换为共享存储实现(如 Redis 缓存),解决跨进程数据一致性问题
11+
*/
12+
public interface GroovyScriptCache {
13+
14+
/**
15+
* 保存脚本对象
16+
*
17+
* @param script 脚本对象
18+
*/
19+
void save(GroovyScript script);
20+
21+
/**
22+
* 获取脚本对象
23+
*
24+
* @param key 脚本key
25+
* @return 脚本对象,不存在时返回 null
26+
*/
27+
GroovyScript get(String key);
28+
29+
/**
30+
* 移除脚本对象
31+
*
32+
* @param key 脚本key
33+
*/
34+
void remove(String key);
35+
36+
/**
37+
* 获取脚本keys
38+
*
39+
* @return 脚本key列表
40+
*/
41+
List<String> keys();
42+
43+
/**
44+
* 脚本总数量
45+
*
46+
* @return 总数量
47+
*/
48+
int count();
49+
50+
/**
51+
* 清空缓存
52+
*/
53+
void clear();
54+
}
Collapse file

‎springboot-starter-script/src/main/java/com/codingapi/springboot/script/cache/GroovyScriptCacheContext.java‎

Copy file name to clipboardExpand all lines: springboot-starter-script/src/main/java/com/codingapi/springboot/script/cache/GroovyScriptCacheContext.java
+24-29Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,29 @@
66
import com.codingapi.springboot.script.temp.TempGroovyScriptContext;
77
import lombok.Getter;
88

9-
import java.util.HashMap;
10-
import java.util.LinkedHashMap;
119
import java.util.List;
12-
import java.util.Map;
1310

1411
/**
1512
* 脚本数据缓存上下文管理对象
13+
* 缓存容器支持替换(如 Redis 共享缓存实现),默认为进程内 LRU 缓存
1614
*/
1715
public class GroovyScriptCacheContext {
1816

1917
@Getter
2018
private final static GroovyScriptCacheContext instance = new GroovyScriptCacheContext();
2119

22-
private final Map<String, GroovyScript> cache;
23-
24-
private final static int MAX_CACHE_SIZE = 10 * 1024;
20+
private GroovyScriptCache cache = new DefaultGroovyScriptCache();
2521

2622
private GroovyScriptCacheContext() {
27-
this.cache = new LinkedHashMap<>(16, 0.75f, true) {
28-
@Override
29-
protected boolean removeEldestEntry(Map.Entry<String, GroovyScript> eldest) {
30-
return size() > MAX_CACHE_SIZE;
31-
}
32-
};
23+
}
24+
25+
/**
26+
* 替换缓存容器实现,入参为 null 时回退为默认 LRU 实现
27+
*
28+
* @param cache 缓存容器实现
29+
*/
30+
public void setCache(GroovyScriptCache cache) {
31+
this.cache = (cache != null) ? cache : new DefaultGroovyScriptCache();
3332
}
3433

3534
/**
@@ -38,9 +37,7 @@ protected boolean removeEldestEntry(Map.Entry<String, GroovyScript> eldest) {
3837
* @param script 脚本对象
3938
*/
4039
public void save(GroovyScript script) {
41-
if (script != null) {
42-
this.cache.put(script.getKey(), script);
43-
}
40+
this.cache.save(script);
4441
}
4542

4643

@@ -50,29 +47,24 @@ public void save(GroovyScript script) {
5047
* @param script 脚本对象
5148
*/
5249
public void cache(GroovyScript script) {
53-
if (script != null) {
54-
this.cache.put(script.getKey(), script);
55-
}
50+
this.cache.save(script);
5651
}
5752

5853

5954
/**
60-
* 删除脚本
55+
* 移除脚本缓存(仅作用于本地缓存容器)
6156
*
6257
* @param key 脚本key
6358
*/
6459
public void remove(String key) {
65-
GroovyScript groovyScript = this.getGroovyScript(key);
66-
if (groovyScript != null) {
67-
this.cache.remove(key);
68-
}
60+
this.cache.remove(key);
6961
}
7062

7163
/**
7264
* 获取脚本keys
7365
*/
7466
public List<String> keys() {
75-
return this.cache.keySet().stream().toList();
67+
return this.cache.keys();
7668
}
7769

7870
/**
@@ -81,7 +73,7 @@ public List<String> keys() {
8173
* @return 总数量
8274
*/
8375
public int count() {
84-
return this.cache.size();
76+
return this.cache.count();
8577
}
8678

8779
/**
@@ -98,7 +90,7 @@ public GroovyScript getGroovyScript(String key) {
9890
if (groovyScript == null) {
9991
groovyScript = GroovyScriptRepositoryContext.getInstance().get(key);
10092
if (groovyScript != null) {
101-
this.cache.put(key, groovyScript);
93+
this.cache.save(groovyScript);
10294
}
10395
}
10496
}
@@ -142,7 +134,7 @@ public void setBatchCache(List<GroovyScript> scriptList) {
142134
if (scriptList != null) {
143135
for (GroovyScript script : scriptList) {
144136
if (script != null) {
145-
this.cache.put(script.getKey(), script);
137+
this.cache.save(script);
146138
}
147139
}
148140
}
@@ -154,8 +146,11 @@ public void setBatchCache(List<GroovyScript> scriptList) {
154146
* @param cache 是否缓存
155147
*/
156148
public void compileAll(boolean cache) {
157-
for (GroovyScript script : this.cache.values()) {
158-
script.compile(cache);
149+
for (String key : this.cache.keys()) {
150+
GroovyScript groovyScript = this.cache.get(key);
151+
if (groovyScript != null) {
152+
groovyScript.compile(cache);
153+
}
159154
}
160155
}
161156

Collapse file

‎springboot-starter-script/src/main/java/com/codingapi/springboot/script/cache/GroovyScriptCacheEvent.java‎

Copy file name to clipboard
+88Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.codingapi.springboot.script.cache;
2+
3+
import com.codingapi.springboot.framework.event.ISyncEvent;
4+
import lombok.Getter;
5+
6+
/**
7+
* 脚本缓存变更事件(同步事件)
8+
* <p>
9+
* 双通道设计:
10+
* 1. 跨进程:脚本在某个实例上保存或删除后经 {@link GroovyScriptCacheSync} SPI 广播,
11+
* 其他实例接收事件后失效本地缓存,下次读取自动回源仓储加载最新版本;
12+
* 2. 进程内:写路径同时经 EventPusher 推送本事件(无 Spring 上下文时为安全 no-op),
13+
* 业务代码可通过实现 IHandler&lt;GroovyScriptCacheEvent&gt; 监听脚本变更。
14+
*/
15+
@Getter
16+
public class GroovyScriptCacheEvent implements ISyncEvent {
17+
18+
public enum Type {
19+
/**
20+
* 脚本保存
21+
*/
22+
SAVE,
23+
/**
24+
* 脚本删除
25+
*/
26+
REMOVE
27+
}
28+
29+
/**
30+
* 事件类型
31+
*/
32+
private final Type type;
33+
34+
/**
35+
* 脚本key
36+
*/
37+
private final String key;
38+
39+
/**
40+
* 事件来源实例标识(用于过滤本实例事件,避免回环)
41+
*/
42+
private final String source;
43+
44+
/**
45+
* 脚本更新时间
46+
*/
47+
private final long updateTime;
48+
49+
private GroovyScriptCacheEvent(Type type, String key, String source, long updateTime) {
50+
this.type = type;
51+
this.key = key;
52+
this.source = source;
53+
this.updateTime = updateTime;
54+
}
55+
56+
/**
57+
* 构建脚本保存事件
58+
*
59+
* @param key 脚本key
60+
* @param source 来源实例标识
61+
* @param updateTime 更新时间
62+
* @return 缓存变更事件
63+
*/
64+
public static GroovyScriptCacheEvent save(String key, String source, long updateTime) {
65+
return new GroovyScriptCacheEvent(Type.SAVE, key, source, updateTime);
66+
}
67+
68+
/**
69+
* 构建脚本删除事件
70+
*
71+
* @param key 脚本key
72+
* @param source 来源实例标识
73+
* @return 缓存变更事件
74+
*/
75+
public static GroovyScriptCacheEvent remove(String key, String source) {
76+
return new GroovyScriptCacheEvent(Type.REMOVE, key, source, 0);
77+
}
78+
79+
/**
80+
* 判断事件是否来自指定实例
81+
*
82+
* @param source 实例标识
83+
* @return 是否来自指定实例
84+
*/
85+
public boolean isFrom(String source) {
86+
return this.source != null && this.source.equals(source);
87+
}
88+
}
Collapse file

‎springboot-starter-script/src/main/java/com/codingapi/springboot/script/cache/GroovyScriptCacheSync.java‎

Copy file name to clipboard
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.codingapi.springboot.script.cache;
2+
3+
/**
4+
* 脚本缓存同步 SPI
5+
* <p>
6+
* 单进程部署使用默认实现 {@link LocalGroovyScriptCacheSync};
7+
* 多实例部署时实现该接口将缓存变更事件广播到其他实例
8+
* (如 Redis pub/sub、MQ 等),并在收到远端事件后调用
9+
* {@link GroovyScriptCacheSyncContext#receive(GroovyScriptCacheEvent)} 失效本地缓存。
10+
*/
11+
public interface GroovyScriptCacheSync {
12+
13+
/**
14+
* 发布脚本缓存变更事件
15+
*
16+
* @param event 缓存变更事件
17+
*/
18+
void publish(GroovyScriptCacheEvent event);
19+
}

0 commit comments

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.