从 p:remoteCommand 的 oncomplete 处理程序调用 JavaScript 函数 - 使用一些 JavaScript 代码模拟相同的函数
Posted
技术标签:
【中文标题】从 p:remoteCommand 的 oncomplete 处理程序调用 JavaScript 函数 - 使用一些 JavaScript 代码模拟相同的函数【英文标题】:Invoking a JavaScript function from oncomplete handler of p:remoteCommand - simulating the same using some JavaScript code 【发布时间】:2015-05-01 19:38:51 【问题描述】:警告:虽然这个问题涵盖了带有大量 Java 代码 sn-ps 的长文本信息,但它只是针对 javascript/jQuery 和一些 PrimeFaces 的东西(只是 <p:remoteCommand>
)作为在开头的介绍部分提到。
我从 WebSockets (Java EE 7 / JSR 356 WebSocket API) 收到如下 JSON 消息。
if (window.WebSocket)
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event)
jsonMsg=event.data;
var json = JSON.parse(jsonMsg);
var msg=json["jsonMessage"];
if (window[msg])
window[msg](); //It is literally interpreted as a function - updateModel();
;
在上面的代码中,event.data
包含一个 JSON 字符串 "jsonMessage":"updateModel"
。因此,msg
将包含一个字符串值,即updateModel
。
在下面的代码段中,
if (window[msg])
window[msg](); //It is literally interpreted as a JavaScript function - updateModel();
window[msg]();
导致调用与 <p:remoteCommand>
关联的 JavaScript 函数(进而调用与 <p:remoteCommand>
关联的 actionListener="#bean.remoteAction"
)。
<p:remoteCommand name="updateModel"
actionListener="#bean.remoteAction"
oncomplete="notifyAll()"
process="@this"
update="@none"/>
update="@none"
不一定需要。
收到此消息后,我需要通知所有关联的客户端有关此更新。我使用以下 JavaScript 函数来执行此操作,该函数与上述 <p:remoteCommand>
的 oncomplete
处理程序相关联。
var jsonMsg;
function notifyAll()
if(jsonMsg)
sendMessage(jsonMsg);
请注意,变量jsonMsg
已经在第一个 sn-p 中分配了一个值 - 它是一个全局变量。 sendMessage()
是另一个 JavaScript 函数,它实际上通过 WebSockets 向所有关联的客户端发送有关此更新的通知,这在本问题中不需要。
这很好用,但有没有办法在以下情况下发挥作用
if (window[msg])
window[msg]();
//Do something to call notifyAll() on oncomplete of remote command.
这样notifyAll()
函数可以直接通过一些 JavaScript 代码调用(当前附加到 <p:remoteCommand>
的 oncomplete
并且预期的 JavaScript 代码(甚至其他东西)应该模拟这个 oncomplete
)基本上无需依赖全局 JavaScript 变量 (jsonMSg
)?
编辑:我试图解决的问题(它可能被认为是附加信息)。
例如,当管理员对名为 Category
的 JPA 实体进行一些更改(通过 DML 操作)时,将触发实体侦听器,进而引发 CDI 事件,如下所示。
@ApplicationScoped
public class CategoryListener
@PostPersist
@PostUpdate
@PostRemove
public void onChange(Category category) throws NamingException
BeanManager beanManager = (BeanManager) InitialContext.doLookup("java:comp/BeanManager");
beanManager.fireEvent(new CategoryChangeEvent(category));
不用说实体Category
是用注释@EntityListeners(CategoryListener.class)
指定的。
只是一个侧面说明(完全偏离主题):通过 JNDI 查找获取 BeanManager
的实例,如前面代码 sn-p 中所做的那样是临时的。具有 Weld 版本 2.2.2 最终版本的 GlassFish Server 4.1 无法注入 CDI 事件 javax.enterprise.event.Event<T>
,该事件应该按如下方式注入。
@Inject
private Event<CategoryChangeEvent> event;
然后,可以参考上面的相关代码sn-p如下触发事件。
event.fire(new CategoryChangeEvent(category));
在web项目中观察到这个事件如下。
@ApplicationScoped
public class RealTimeUpdate
public void onCategoryChange(@Observes CategoryChangeEvent event)
AdminPush.sendAll("updateModel");
管理员使用自己的端点如下(AdminPush.sendAll("updateModel");
在其中手动调用)。
@ServerEndpoint(value = "/AdminPush", configurator = ServletAwareConfig.class)
public final class AdminPush
private static final Set<Session> sessions = new LinkedHashSet<Session>();
@OnOpen
public void onOpen(Session session, EndpointConfig config)
if (Boolean.valueOf((String) config.getUserProperties().get("isAdmin")))
sessions.add(session);
@OnClose
public void onClose(Session session)
sessions.remove(session);
private static JsonObject createJsonMessage(String message)
return JsonProvider.provider().createObjectBuilder().add("jsonMessage", message).build();
public static void sendAll(String text)
synchronized (sessions)
String message = createJsonMessage(text).toString();
for (Session session : sessions)
if (session.isOpen())
session.getAsyncRemote().sendText(message);
此处仅允许管理员使用此端点。使用onOpen()
方法中的条件检查阻止所有其他用户创建 WebSocket 会话。
foreach
循环内的session.getAsyncRemote().sendText(message);
向管理员发送通知(以 JSON 消息的形式)关于实体 Category
中所做的这些更改。
如第一个代码 sn-p 所示,window[msg]();
调用与应用程序范围 bean - actionListener="#realTimeMenuManagedBean.remoteAction"
关联的操作方法(通过如前所示的 <p:remoteCommand>
)。
@Named
@ApplicationScoped
public class RealTimeMenuManagedBean
@Inject
private ParentMenuBeanLocal service;
private List<Category> category;
private final Map<Long, List<SubCategory>> categoryMap = new LinkedHashMap<Long, List<SubCategory>>();
// Other lists and maps as and when required for a dynamic CSS menu.
public RealTimeMenuManagedBean()
@PostConstruct
private void init()
populate();
private void populate()
categoryMap.clear();
category = service.getCategoryList();
for (Category c : category)
Long catId = c.getCatId();
categoryMap.put(catId, service.getSubCategoryList(catId));
// This method is invoked through the above-mentioned <p:remoteCommand>.
public void remoteAction()
populate();
// Necessary accessor methods only.
只有在actionListener="#realTimeMenuManagedBean.remoteAction"
完全完成时才应通知所有其他用户/客户(位于不同面板上 - 除了管理面板) - 不得在操作方法完成之前发生 - 应通知通过<p:remoteCommand>
的oncomplate
事件处理程序。这就是采取两个不同端点的原因。
其他用户使用自己的端点:
@ServerEndpoint("/Push")
public final class Push
private static final Set<Session> sessions = new LinkedHashSet<Session>();
@OnOpen
public void onOpen(Session session)
sessions.add(session);
@OnClose
public void onClose(Session session)
sessions.remove(session);
@OnMessage
public void onMessage(String text)
synchronized (sessions)
for (Session session : sessions)
if (session.isOpen())
session.getAsyncRemote().sendText(text);
@OnMessage
注解的方法发挥作用,如上图通过<p:remoteCommand>
的oncomplete
发送消息。
那些客户端使用以下 JavaScript 代码从上述应用程序范围的 bean 中获取新值(该 bean 已经被管理员从数据库中充分查询过。因此,没有必要通过以下方式再次荒谬地查询它每个单独的客户端(管理员除外)。因此,它是一个应用程序范围的 bean)。
if (window.WebSocket)
var ws = new WebSocket("wss://localhost:8181/ContextPath/Push");
ws.onmessage = function (event)
var json = JSON.parse(event.data);
var msg = json["jsonMessage"];
if (window[msg])
window[msg]();
;
$(window).on('beforeunload', function ()
ws.close();
);
结合以下<p:remoteCommand>
.
<p:remoteCommand name="updateModel"
process="@this"
update="parentMenu"/>
其中parentMenu
- 由此<p:remoteCommand>
更新的组件是容器JSF 组件<h:panelGroup>
的id
,其中包含一个带有一堆<ui:repeat>
s 的纯CSS 菜单。
希望这能让场景更清晰。
更新:
这个问题已经根据<p:remoteCommand>
准确回答了here(至于具体问题,唯一的问题是删除对全局 JavaScript 变量的依赖,如本问题的介绍部分所述)。
【问题讨论】:
我很难看到/理解您要解决的问题。您基本上是在来回发送推送消息。为什么?要求真的如下:“只推送到一个客户端,完成后,将完全相同的消息推送到所有其他客户端”?我想知道您为什么不首先推送给所有客户。 一个快速的想法可能是在调用updateModel
时将jsonMsg
作为请求参数发送,而updateModel
将在args var 中重新发送jsonMsg
,因此oncomplete 将从 args
对象中获取该 var,因为它已经在 oncomplete 参数中可用。这样你会限制对 jsonMsg 的访问,并且它只能在 oncomplete 函数中使用!
@BalusC :这个观察是正确的。这就是在这种情况下需要的方式 - 当相关数据库中进行一些更改时(由管理员。只有管理员被允许(或具有特权)进行这些更改),所有其他客户端(匿名用户和/或经过身份验证的用户)用户)将在进行这些更改后立即收到通知,换句话说,只有在管理员(及时)进行这些更改后才必须通知所有其他用户 - 仅在该数据库操作完成后成功提交 - 不是在提交之前那么明显。
我理解那部分。但是,如果请求只能来自管理员,为什么不首先推送给所有客户端呢?
@BalusC :唯一的管理员和所有其他用户都有不同的端点。不允许其他用户使用属于该管理员唯一的管理员的端点(即没有其他用户可以在此端点中拥有 WebSocket 会话。他们被阻止。所有其他用户使用单独的端点点代替)。更新由给定的<p:remoteCommand>
进行,当它的actionListener="#bean.remoteAction"
被触发时(即<p:remoteCommand>
),只有当管理员接收到 WebSocket 消息时才会触发(或执行)(例如:通过 JPA 实体侦听器) )。
【参考方案1】:
我不认为我了解您问题的各个方面,但无论如何我会尽力提供一些帮助。请注意,我不知道 PrimeFaces,所以我所做的只是阅读文档。
我的理解是,你试图摆脱全局变量。但恐怕,我认为这是不可能的。
这里的问题是,PrimeFaces 不允许您将远程调用的调用透明地传递给 oncomplete
调用(除非您将其传递给 Bean 的 Java 代码,然后再传回 UI ,而这通常不是你想要的)。
但是,我希望,你可以非常接近它。
第 1 部分,JS 早早回归
还请注意,对 Java 和 JavaScript 可能存在一些误解。
Java 是多线程的,可以并行运行多个命令,而 JavaScript 是单线程的,通常从不等待某事完成。异步处理是获得响应式 Web-UI 的必要条件。
因此,您的 remoteCommand
调用(从 JS 端看)将(通常是异步情况)在 oncomplete
处理程序被调用之前很久就返回。这意味着,如果window[msg]()
返回,您还没有完成remoteCommand
。
那么你想用下面的代码管理什么
if (window[msg])
window[msg]();
//Do something to call notifyAll() on oncomplete of remote command.
dosomethinghere();
会失败。当remoteCommand
返回时,dosomethinghere()
不会被调用(因为 JS 不想等待某个事件,这可能永远不会发生)。这意味着,当 Ajax 请求刚刚打开到远程(Java 应用程序)时,dosomethinghere()
将被调用。
要在 Ajax 调用完成后运行某些内容,必须在 oncomplete
例程(或 onsuccess
)中完成。这就是它存在的原因。
第 2 部分,验证 msg
请注意window[msg]()
的不同之处。如果您不能完全信任推送的消息,这可能会被认为有点危险。 window[msg]()
本质上运行任何以变量msg
的内容命名的函数。例如,如果 msg
恰好是 close
,则将运行 window.close()
,这可能不是您想要的。
您应该确保msg
是一个预期的词,并拒绝所有其他词。示例代码:
var validmsg = updateModel:1, rc:1
[..]
if (validmsg[msg] && window[msg])
window[msg]();
第 3 部分:如何并行处理多个 JSON 消息
全局变量有一些缺点。只有一个。如果您碰巧在 WebSocket 上收到另一条 JSON 消息,而前一条消息仍在 remoteCommand
中处理,这将覆盖前一条消息。所以notifyAll()
会看到新消息两次,旧消息丢失。
经典的竞争条件。你必须做的是,创建一个类似注册表的东西来注册所有的消息,然后将一些值传递给notifyAll()
来告诉哪些注册的消息应该被处理。
只需稍作改动,您就可以并行(此处)或串行(第 4 部分)处理消息。
首先,创建一个能够区分消息的计数器。也是一个存储所有消息的对象。我们声明所有我们期望的有效消息(参见第 2 部分):
var jsonMsgNr = 0;
var jsonMessages = ;
var validmsg = updateModel:1
现在每次我们收到一条消息时添加一条消息:
if (window.WebSocket)
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event)
var jsonMsg = event.data;
var json = JSON.parse(jsonMsg);
var msg=json["jsonMessage"];
if (validmsg[msg] && window[msg])
var nr = ++jsonMsgNr;
jsonMessages[nr] = jsonMsg:jsonMsg, json:json ;
为了能够将nr
传递给NotifyAll()
,需要将一个附加参数传递给Bean。我们就叫它msgNr
:
// Following might look a bit different on older PrimeFaces
window[msg]([name:'msgNr', value:nr]);
也许查看https://***.com/a/7221579/490291 以了解更多关于以这种方式传递值的信息。
remoteAction
bean 现在获得了一个附加参数 msgNr
传递,它必须通过 Ajax 传回。
不幸的是,我不知道(抱歉)它在 Java 中的外观。因此,请确保您对 AjaxCall 的回答再次将
msgNr
复制出来。另外,由于文档对这个主题很安静,我不确定参数是如何传递回
oncomplete
处理程序的。根据 JavaScript 调试器,notifyAll()
有 3 个参数:xhdr
、payload
和pfArgs
。不幸的是,我无法设置测试用例来了解情况。
因此函数看起来有点像(请耐心等待):
function notifyAll(x, data, pfArgs)
var nr = ???; // find out how to extract msgNr from data
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
jsonMessages[nr] = null; // free memory
sendMessage(jsonMsg);
dosomething(json);
如果将其拆分为两个函数,则可以从应用程序的其他部分调用notifyAll()
:
function notifyAll(x, data, unk)
var nr = ???; // find out how to extract msgNr from data
realNotifyAll(nr);
function realNotifyAll(nr)
if (!(nr in jsonMessages)) return;
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
delete jsonMessages[nr]; // free memory
sendMessage(jsonMsg);
dosomething(json);
这里有些东西有点多余。例如,您可能不需要
jsonMessages
中的json
元素,或者想再次解析json
以节省一些内存,以防json 非常大。但是,该代码并不是最佳的,而是易于根据您的需要进行调整。
第 4 部分:序列化请求
现在来进行序列化的更改。通过添加一些信号量,这很容易。 JavaScript 中的信号量只是变量。这是因为只有一个全局线程。
var jsonMsgNr = 0;
var jsonMessages = ;
var validmsg = updateModel:1
var jsonMsgNrLast = 0; // ADDED
if (window.WebSocket)
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event)
var jsonMsg = event.data;
var json = JSON.parse(jsonMsg);
var msg=json["jsonMessage"];
if (validmsg[msg] && window[msg])
var nr = ++jsonMsgNr;
jsonMessages[nr] = jsonMsg:jsonMsg, json:json ;
if (!jsonMsgNrLast) // ADDED
jsonMsgNrLast = nr; // ADDED
window[msg]([name:'msgNr', value:nr]);
function realNotifyAll(nr)
if (!(nr in jsonMessages)) return;
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
delete jsonMessages[nr]; // free memory
sendMessage(jsonMsg);
dosomething(json);
// Following ADDED
nr++;
jsonMsgNrLast = 0;
if (nr in jsonMessages)
jsonMsgNrLast = nr;
window[jsonMessages[nr].json.msg]([name:'msgNr', value:nr]);
注意:jsonMsgNrLast
可能只是一个标志(真/假)。但是,将当前处理的数字放在变量中可能会有所帮助。
话虽如此,如果sendMessage
或dosomething
发生故障,则会出现饥饿问题。所以也许你可以把它交错一下:
function realNotifyAll(nr)
if (!(nr in jsonMessages)) return;
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
delete jsonMessages[nr]; // free memory
nr++;
jsonMsgNrLast = 0;
if (nr in jsonMessages)
jsonMsgNrLast = nr;
// Be sure you are async here!
window[jsonMessages[nr].json.msg]([name:'msgNr', value:nr]);
// Moved, but now must not rely on jsonMsgNrLast:
sendMessage(jsonMsg);
dosomething(json);
这样,当sendMessage
正在运行时,AJAX 请求就已经发送出去了。如果现在dosomething
出现 JavaScript 错误或类似错误,消息仍会被正确处理。
请注意:所有这些都是在没有任何测试的情况下输入的。可能存在语法错误或更糟。对不起,我尽力了。如果您发现错误,编辑就是您的朋友。
第 5 部分:从 JS 直接调用
现在,所有这些都到位并进行了序列化运行,您始终可以使用realNotifyAll(jsonMsgNrLast)
调用以前的notifyAll()
。或者您可以在列表中显示jsonMessages
并选择任意数字。
通过跳过对window[jsonMessages[nr].json.msg]([name:'msgNr', value:nr]);
(及以上window[msg]([name:'msgNr', value:nr]);
)的调用,您还可以停止Bean 处理并使用通常的JQuery 回调按需运行它。为此创建一个函数并再次更改代码:
var jsonMsgNr = 0;
var jsonMessages = ;
var validmsg = updateModel:1
var jsonMsgNrLast = 0;
var autoRun = true; // ADDED, set false control through GUI
if (window.WebSocket)
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event)
var jsonMsg = event.data;
var json = JSON.parse(jsonMsg);
if (validmsg[msg] && window[msg])
var nr = ++jsonMsgNr;
jsonMessages[nr] = jsonMsg:jsonMsg, json:json ;
updateGuiPushList(nr, 1);
if (autoRun && !jsonMsgNrLast)
runRemote(nr);
function realNotifyAll(nr)
if (!(nr in jsonMessages)) return;
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
delete jsonMessages[nr]; // free memory
updateGuiPushList(nr, 0);
jsonMsgNrLast = 0;
if (autoRun)
runRemote(nr+1);
// Moved, but now must not rely on jsonMsgNrLast:
sendMessage(jsonMsg);
dosomething(json);
function runRemote(nr)
if (nr==jsonMsgNrLast) return;
if (nr in jsonMessages)
if (jsonMsgNrLast) alert("Whoopsie! Please wait until processing finished"); return;
jsonMsgNrLast = nr;
updateGuiPushList(nr, 2);
// Be sure you are async here!
window[jsonMessages[nr].json.msg]([name:'msgNr', value:nr]);
现在您可以使用runRemote(nr)
开始处理并使用realNotifyAll(nr)
调用完成函数。
函数updateGuiPushList(nr, state)
和state=0:finished 1=added 2=running
是对GUI 代码的回调,它会更新屏幕上等待处理的推送列表。设置autoRun=false
停止自动处理,设置autoRun=true
自动处理。
注意:将autoRun
从false
设置为true
后,当然需要用最低的nr
触发一次runRemote
。
【讨论】:
我暂时避免接受答案。我需要相当长的时间才能回到这个话题。感谢您提供详细的答复。以上是关于从 p:remoteCommand 的 oncomplete 处理程序调用 JavaScript 函数 - 使用一些 JavaScript 代码模拟相同的函数的主要内容,如果未能解决你的问题,请参考以下文章
p:remoteCommand 销毁 @ViewScoped 托管 bean
错误:QueryReqWrap.onresolve 处的 querySrv ENODATA _mongodb._tcp.blog-cluster-0hb5z.mongodb.net [as oncom