我有一个portlet。当portlet加载时,则在呈现第一个视图之前,在某些情况下需要调用更改数据库中数据的存储库。我不会详细说明为什么这是必要的,关于这是一个设计缺陷的答案是没有用的。我知道这是一个设计缺陷,但我仍然想找到解决以下问题的替代方案:
此设置的问题是,浏览器发送预加载请求。例如,portlet所在页面的URL是/ test-portlet。现在,当您在地址栏中键入它时,如果您在浏览器历史记录中输入它,则浏览器会在向您建议时向页面发送GET请求。如果在解析第一个GET请求之前按Enter键,则浏览器会发送新的GET请求。这意味着portlet接收2个独立的请求,它们开始并行处理。第一个数据库过程可能正常工作,但考虑到数据库过程的性质,第二个调用通常会给出异常。
从Java应用程序处理上述问题会有什么好的干净方法?
旁注:我正在使用Spring MVC。
可能的控制器的一个简单示例:
@RequestMapping
public String index( Model model, RenderRequest request ){
String username = dummyRepository.changeSomeData(request.getAttribute("userId"));
model.add("userName", username);
return "view";
}
我会对阻止第一次执行的解决方案感兴趣。例如,某些从控制器重定向到POST的浏览器不会触发。不确定它是否可以实现。
答案 0 :(得分:1)
使用锁我认为你可以解决它,使得secound请求等待第一个完成然后处理它。我没有java中锁的经验,但我在jave中发现了另一个关于文件锁的堆栈交换帖: How can I lock a file using java (if possible)
答案 1 :(得分:1)
请参阅this answer,它可能会帮助您检测并忽略某些预加载请求。但是你也应该确保'最坏情况'有效,也许使用@jpeg建议的锁定,但它可以像在某处使用synchronize
块一样简单。
答案 2 :(得分:1)
由于我没有看到chrome添加了一些特定的标题(或者无论如何通知服务器有关预呈现状态),因此可能无法在服务器端检测到它...至少不能直接检测到它。但是,您可以在客户端模拟检测,然后将其与服务器调用结合起来。
请注意,您可以在客户端检测预呈现:
if (document.webkitVisibilityState == 'prerender' || document.visibilityState == 'prerender' || document.visibilityState[0] == 'prerender') {
// prerendering takes place
}
现在,您可以通过在浏览器处于预加载状态时显示警告框来中断客户端的预加载(或者您可以在javascript中仅使用一些错误而不是使用alert()来执行相同操作):
if (document.webkitVisibilityState == 'prerender' || document.visibilityState == 'prerender' || document.visibilityState[0] == 'prerender') {
alert('this is alert during prerendering..')
}
现在当chrome预渲染页面时,它会失败,因为javascript警报会阻止浏览器继续执行javascript。
如果您输入chrome: chrome:// net-internals / #prerender ,您可以跟踪Chrome执行预渲染的时间和页面。在上述示例的情况下(在预渲染期间使用警告框),您可以看到:
Link Rel Prerender(交叉 域名)http://some.url.which.is.preloaded Javascript 提醒 2015-06-07 19:26:18.758
最终状态 - Javascript Alret证明chrome无法预加载页面(我已对此进行了测试)。
现在如何解决您的问题?好吧,你可以将它与异步调用(AJAX)结合起来并加载一些内容(来自另一个url),具体取决于页面实际上是否正在预渲染。
考虑以下代码(可能由您的portlet在url / test-portlet下呈现):
<html>
<body>
<div id="content"></div>
<script>
if (document.webkitVisibilityState == 'prerender' || document.visibilityState == 'prerender' || document.visibilityState[0] == 'prerender') {
// when chrome uses prerendering we block the request with alert
alert('this is alert during prerendering..');
} else {
// in case no prerendering takes place we load the actual content asynchronously
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// when the content is loaded we place the html inside "content" div
document.getElementById('content').innerHTML = xhr.responseText;
}
}
xhr.open('GET', '/hidden-portlet', true); // we call the actual portlet
xhr.send(null);
}
</script>
</body>
</html>
如您所见,只有在浏览器正常加载页面(没有预加载)的情况下才会加载/ hidden-portlet。 url / hidden-portlet下的服务器端处理程序(可以是另一个portlet / servlet)包含在预渲染期间不应执行的实际代码。所以它是/ hidden-portlet执行
dummyRepository.changeSomeData(request.getAttribute("userId"));
这个portlet还可以返回普通视图(呈现的html),由于/ test-portlet上的技巧document.getElementById('content').innerHTML = xhr.responseText;
,它将被异步放置在url / test-portlet下的页面上。
因此,在地址/ test-portlet下sumarize portlet只返回带有触发实际portlet的javascript代码的html。
如果你有许多脆弱的portlet,你可以更进一步,所以你可以使用像/test-portlet?actualUrl=hidden-portlet
这样的请求参数来参数化/ test-portlet,这样实际的portlet的地址就可以从url中获取(可以在服务器端读取请求参数)。在这种情况下,服务器将动态呈现应加载的URL:
所以不是硬编码:
xhr.open('GET', '/hidden-portlet', true);
你会有
xhr.open('GET', '/THIS_IS_DYNAMICALLY_REPLACED_EITHER_ON_SERVER_OR_CLIENT_SIDE_WITH_THE_ADDRES_FROM_URL', true);