远程轮询在ManagedBean中并通过推送通知客户端视图

9
我有一个JSF视图,其中显示了来自管理bean(视图范围)的一些数据表格,该表格是通过远程检索的。 目前,数据通过使用PrimeFaces轮询组件从客户端视图进行更新。但这还不够,因为向客户端发送了太多的流量,现在PrimeFaces支持服务器推送,我只想在数据更改时重新加载数据并将其推送到客户端视图中。 这应该通过从Web层到应用程序层调用像“hasChanged(...)”这样的方法进行轮询来实现。如果数据发生更改,则Web层向客户端推送通知以重新加载数据。 当前客户端轮询如下: 客户端 >> Web层 >> 应用层 (客户端通过AJAX请求Web层获取数据,Web层再次请求应用层获取数据并更新视图) 期望的Web层轮询和推送如下: 客户端 << Web层 >> 应用层 (Web层轮询应用层是否更改了数据,并代表重新加载数据并通知(推送)客户端更新视图) 方法:

如何在Web层中实现托管bean的轮询最佳方法?

  1. 在托管bean中使用TimerTask
    在JSF托管bean中生成线程以进行定时任务的方法
  2. 使用Schedule注释的其他EJB
  3. 使用TimerService的其他EJB
  4. 其他方法?

编辑:

架构:(3层)

  • 服务器1:数据库
  • 服务器2:应用层(带有远程EJB + Hibernate的EAR)
  • 服务器3:Web层(带有JSF 2.0 + Primefaces 3.4的WAR)
  • 客户端:浏览器

Architecture


我在ejb-tier中完成了push ejb(将atmosphere依赖项添加到ejb.jar中)。请参见:http://stackoverflow.com/questions/12294909/access-pushcontext-from-ejb-tier/12998971#12998971 - Aksel Willgert
我添加了一些关于我的架构的信息。一个服务器,应用程序层有一个带有远程EJB的EAR文件,而Web层服务器只有一个WAR文件。 - djmj
啊,现在我明白了。虽然我没有尝试过这个设置,但如果你只是要从EJB推送,我认为你可以更多或更少地遵循我上面链接的解决方案。不同之处在于Atmosphere必须在Server2-app-tier上运行,并在web.xml中将push-server设置为指向server2而不是web-server <context-param> <param-name>primefaces.PUSH_SERVER_URL</param-name> <param-value>http://localhost:8080</param-value> </context-param> - Aksel Willgert
我认为(尚未尝试)它可以在单独的服务器上运行,即不需要与.war捆绑在一起。 - Aksel Willgert
不,我不想直接访问这个服务器,因为这会破坏三层范例。通信必须通过 Web 层(server3(图中的 A?))。这就是为什么我问如何实现 Web 层和应用层之间的轮询的原因。 - djmj
显示剩余6条评论
1个回答

3
根据我的经验,我可以推荐两种方法,Spring Integration和CDI事件。虽然我建议选择Spring路线,但基于您当前的技术栈,我认为CDI事件已经满足您的需求。它们可以干净地帮助您实现观察者/可观察者模式,并且您还可以实现层之间的干净分离。但是,我必须警告您,这种方法只对小中型用例有效。请考虑以下内容:
  1. Design and implement an Event class that encapsulates all the information that is required to be delivered to consumers. Let's call it a FundsTransfer event Your event implementation should contain enough information to enable listeners filter only for events of interest. A simple POJO

    class FundsTransfer {
    
        BigDecimal transferValue;
        Date transferDate;
        int transferCurrencyCode;
    
        public FundsTransfer(BigDecimal transferValue, Date date, int currencyCode) {
            //set accordingly
        }
        //setters and getters
    }
    
  2. Implement a business object at the business layer, call it Notifier. The function of polling should be delegated to this object. It will be in charge of creating and publishing objects of type event in response to changes on the server side. Depending on your requirements, this object could be a singleton to handle all Event types or you could have a group of Notifier types polling for different events.

    //A sample implementation of your Observer object :
    @Singleton //Defines a singleton EJB
    public class PollerService {
    
        @Inject
        Event fundsTranferNotifier;
    
        //this annotation specifies that the polling method should run every second.
        @Schedule(second = "*/1", minute = "*", hour = "*", persistent = false)
        public void pollIt() {
            boolean found = this.pollingMethod(); // pollingMethod() will do the actual polling
            if (found) {     //based on the outcome of the polling method, fire the notifier  method
                blowWhistleOnTransfer();
            }
        }
    
        public void blowWhistleOnTransfer() {
            //this is the broadcast event.
            fundsTransferNotifier.fire(new FundsTransfer(new BigDecimal("100000", new Date(), 855));
        }
    }
    

    In the code above, I've used a Timer EJB as my Observer. See this for an introduction to EJB timers. Again, the Observer object will live in the app tier

  3. The client tier will each have access to an listener object that will be notified when an event of interest occurs (that is has been published by a Notifier type). Then the listener can issue a push based on this event. Your listener object could be a POJO with @Named CDI annotation. In your listener object, simply implement a method with an @Observes annotation, with a parameter of the type of event the listener is interested in:

    public void onNewTransfer(@Observes FundsTransfer transfer) {
        if (transfer.compareTo(new BigDecimal("150000")) > 0) {
            //push to view.
        }
    }
    

    The above filtering is still quite crude compared to the message filtering options that CDI provides. As you can see from the tutorial I referenced earlier, you could create CDI qualifiers that can give you finer grained filtering on messages. Like I stated earlier, this is a bit heavy for large scale deployment, in which case I'd advise the spring integration route, if you're up to take the dependency on.

总的来说,该系统的模型将会是:

            One Poller(Notifier) Object (In the app layer)
                    |
                    |
                    |
            Multiple Listener Objects (In the web tier)
---------------------------------------------------
|   |    |    |    |   |   |     |   |    |    |  |  

这个观察者模式是否在两个不同的服务器应用层和Web层之间起作用(远程观察者模式)?在业务层实现一个业务对象,称之为Notifier。轮询功能应该委托给这个对象。我的最初问题是如何解决轮询?我有点困惑或者经验不足以理解你的答案。 - djmj
@djmj,Notifier对象将位于应用程序层。它应该进行轮询/检查以查找更改。根据其发现,它将在整个JEE上下文中广播消息。Observer对象将根据Notifier的广播接收Event实例/通知。Observer对象将位于Web层,并根据Event实例,您可以使用primefaces push来影响客户端的更改。 - kolossus
@djmj,我已经在我的答案中添加了更多的实现细节。可能是我导致了你的困惑,应用程序层中的对象是观察者,并将执行实际的轮询。Web 层中的对象只是监听器,用于监听在应用程序/业务层中生成的“事件”。这些监听器将被注入到您的 JSF 托管 bean 中。 - kolossus
我在我的app-tier中创建了一个名为Notifier的单例EJB,它具有定时方法和事件Event<String>(定期触发事件并打印到控制台)。在我的web-tier中,我创建了一个有状态的EJB Listener,其中包含@Observes String str方法,该方法将字符串打印到控制台。该ListenerEJB使用@EJB注入到托管bean中,在我的视图中调用该bean。当然什么也没有发生。这个EJB应该如何被远程单例通知?对于以前的所有远程EJB访问,我都必须使用jndi查找来建立连接,在这种情况下我该怎么办? - djmj
谢谢提供链接。我会在接下来的一周内研究一下。 - djmj
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接