我正在开发一个java应用程序,需要向外部API提交post请求。我遇到的问题是,如果执行该方法的事件被触发多次(例如用户意外双击),我们最终会向外部系统提交重复的数据。
我需要一种在调用方法时存储标识键(例如 ID)的方法,然后防止使用该键进行任何其他调用,直到当前执行完成。
供参考,我的函数如下所示:
public static SendCreateMessageResponse createMessage(String baseUrl, String application, String env, String type, String channel, String modoToken, Message message, ArrayList<ChannelDatum> personalChannels) {
PersonalMessageRequest pemr = null;
pemr = new PersonalMessageRequest();
PersonalMessage pm = new PersonalMessage();
pm.setTitle(message.getTitle());
pm.setBody(message.getBody());
logger.debug("Send message to modo: " + pm);
pemr.setPersonal_message(pm);
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = null;
ObjectMapper om = new ObjectMapper();
Response response = null;
try {
if(pmr!=null) {
logger.debug(om.writeValueAsString(pmr));
body = RequestBody.create(mediaType, om.writeValueAsString(pmr));
}
logger.info("Calling URL: "+baseUrl+application+"/"+env+"/"+type+"/channels/"+channel+"/messages");
logger.trace("Sending data: " + body);
Request request = new Request.Builder()
.url(baseUrl+application+"/"+env+"/"+type+"/channels/"+channel+"/messages")
.method("POST", body)
.addHeader("accept", "application/vnd.modo.communicate.v2")
.addHeader("Content-type", "application/json")
.addHeader("Authorization", "Bearer "+modoToken)
.build();
//Note that this is not asynchronous. execute will block until the response returns or //fails
response = client.newCall(request).execute();
String jsonResponse = response.body().string();
logger.debug(jsonResponse);
SendCreateMessageResponse messagesResponse = om.readValue(jsonResponse, SendCreateMessageResponse.class);
return messagesResponse;
} catch (IOException e) {
logger.error("Error creating message", e);
} finally {
if(response!=null) {
if(response.body()!=null) {
response.body().close();
}
}
}
return null;
}
多次触发(例如用户不小心双击
只需在第一次单击时禁用按钮即可。任务完成后,启用按钮。
如果您特定选择的 GUI 框架存在点击泄漏问题,则每次点击都可能会发布到队列中。
每次单击时,通过
Runnable
方法将一个对象(可能是 Callable
/
offer
)发布到容量受限的队列。
如果队列的容量限制设置为 1,则对第二个元素调用
offer
将被拒绝,返回 false
。
在第一个对象完成工作之前,不要从队列中删除该对象。
如果您确实无法从源头(客户端)解决问题,您可以将每个帖子收集到过期的缓存中。当服务器收到帖子时,它首先检查缓存是否匹配。如果匹配,则拒绝/忽略该帖子。如果不匹配,则将该帖子添加到缓存中,然后处理该帖子。
您会在 Google Guava 中找到过期的缓存实现,也许在 Eclipse Collections 中。显然 Spring 还提供了缓存以及生存时间功能。
您可以编写自己的后台线程来重复清除缓存。但我建议使用已经编写、测试和部署的库中的现有库。
两种处理过期缓存的方法均在以下位置讨论:带有过期键的 Java 基于时间的映射/缓存
这种模式被称为
debouncing
。您可以用 java 编写一个简单的防抖器,由 ScheduledExecutorService
支持,它可以在缓存中保留一段时间。
public class Debouncer {
private final long duration;
private final TimeUnit unit;
private final ScheduledExecutorService executor;
private final ConcurrentMap<Object, Future<Object>> cache = new ConcurrentHashMap<>();
// constructor
public Object debounce(Object key, Callable<Object> callable) throws Exception {
Function<Object, Future<Object>> function = k -> {
Future<Object> future = executor.submit(callable);
executor.schedule(() -> cache.remove(k), duration, unit);
return future;
};
Future<Object> future = cache.computeIfAbsent(key, function);
return future.get();
}
}
假设您有以下 API
public interface Api {
String methodOne(String arg1, String arg2);
Date methodTwo(Long arg1);
}
你可以用
包裹它public class DebouncingApi implements Api {
private final Api delegate;
private final Debouncer debouncer;
// constructor
public String methodOne(String arg1, String arg2) {
String key = List.of(arg1, arg2);
return (String) debouncer.debounce(key, () -> delegate.methodOne(arg1, arg2));
}
public Date methodTwo(Long arg1) {
return (Date) debouncer.debounce(arg1, () -> delegate.method2(arg1));
}
}