【问题标题】:Vaadin 8 : reload grid with data from server every 1minVaadin 8:每 1 分钟从服务器重新加载网格数据
【发布时间】:2019-01-18 13:09:20
【问题描述】:

我正在尝试为网格设置自动刷新功能,它基本上每隔 'n' 秒使用来自服务器的最新数据更新网格。

每当用户启用自动刷新时,我都能实现 PollListner。

                UI ui= TestUI.getCurrent();
                Boolean value = isRefreshChkBox.getValue();
                PollListener listener = e -> {
                    explorer.reloadUI();
                };
                if (value) {

                    String refreshRateValue = refreshRateTxtField.getValue();
                    int refreshRate = Integer.valueOf(refreshRateValue);
                    int millis = (int) TimeUnit.SECONDS.toMillis(refreshRate);

                    absUI.setPollInterval(millis);

                    absUI.addPollListener(listener);
                } else {
                    absUI.setPollInterval(-1);
                    absUI.removePollListener(listener);
                }

使用上面的代码,我每次启用自动刷新时都添加 PollListener,并在禁用时将其删除。

我在这里找到了类似的问题VAADIN 7: What is the simplest way to refresh a Vaadin View in 5 minute intervals?

但我想了解的是,有没有更好的方法来实现简单的用例 AutoRefresh UI? PollListener 应该在哪里实现?我想为视图创建一次 PollListener,每次用户更改刷新率时更新 PollInterval。

关于哪种方法更好或 Vaadin 中是否有全新的概念来实现这一点的任何指针?

TIA

【问题讨论】:

    标签: java refresh vaadin polling vaadin8


    【解决方案1】:

    请参阅correct Answer by Leif Åstrand。我将添加一些讨论,以及一个使用轮询和推送的完整示例应用程序。

    Vaadin 8 有两种无需用户做出手势即可自动更新信息显示的方式:轮询和推送。

    轮询

    在 Vaadin 8 的 Polling feature 中,set a polling interval 在 UI 子类上的毫秒数。 -1 的默认值禁用轮询。

    myUI.setPollInterval( ( int ) TimeUnit.MINUTES.toMillis( 1 ) );  // Specify milliseconds for polling interval.
    

    启用后,安装在用户 Web 浏览器中的 Vaadin JavaScript 库会与 Vaadin 服务器签入。作为PollNotifier,UI 签入会导致在服务器端触发一个事件。

    如果您定义一个实现PollListener 接口的类,您的实例将调用其poll 方法。

    注册您的PollListener 后。取回 Registration 对象。如果需要,该对象提供了一个 remove 方法来取消注册您的侦听器。

    您可以选择使用 lambda 语法、匿名内部类或单独定义的类来定义您的 PollListener。

    Registration registration = this.addPollListener( new UIEvents.PollListener() {
        @Override
        public void poll ( UIEvents.PollEvent pollEvent ) {
            System.out.println( "TRACE - PollListener::poll running. " + Instant.now() );
            …
        }
    } );
    

    或者,lambda 语法:

    Registration registration = this.addPollListener( ( UIEvents.PollListener ) pollEvent -> {
        System.out.println( "TRACE - PollListener::poll running. " + Instant.now() );
        …
    } );
    

    在此调用期间,您的代码可以注册一个Runnable,以便在方便的时候使用您的UI 子类调用。

    Runnable 负责更新 UI 子类中包含的小部件。请记住切勿从后台线程访问或修改小部件。你可能会侥幸逃脱,或者你可能会导致可怕的事情发生。安全:始终调用 UI::access 以传递访问小部件的 Runnable。 Runnable 将在您的 Web 应用程序的主用户界面线程上运行,该线程负责您的 UI 子类实例。

    getUI().access( new Runnable() {
                        @Override
                        public void run ( ) {
                            subscriber.refresh( new ArrayList <>( statusList ) ); // Copy the list in case the `Grid` modifies it, such as sorting.
                        }
                    } );
    

    优点

    使用轮询功能的好处是,您必须进行的编程比使用Push(下文讨论)更简单。在了解自动非用户生成更新时,轮询可能是一种更好的途径。

    一个简单的方面是UI 子类的每个实例都负责自己的轮询,选择是否以及何时进行轮询并控制轮询的频率。每个UI 子类实例调用自己的setPollInterval 方法。更多的轮询可能对用户有好处,但是聊天会增加网络流量,从而使您的网络管理员变得暴躁。所以你可以通过UI子类实例来调整频率。请记住,不仅每个用户都有自己的UI 子类实例,而且 Vaadin 8 还能够支持多窗口/标签应用程序。每个 Web 浏览器中的一个 Web 应用程序可以打开多个窗口/选项卡,每个窗口/选项卡都运行自己的相同或不同 UI 子类的实例。

    缺点

    在美学上的一个缺点是轮询破坏了 HTTP 设计的请求-响应优雅。虽然这是我的烦恼,但那艘船很久以前就已经航行了,所以我不会在这里浪费字节来抱怨使用文档传递系统作为交互式客户端-服务器应用程序架构。

    一个更实际的缺点是网络上的不必要流量。如果您能够通过WebSocket 或Webpush 使用Push,那么客户端和服务器之间的开放连接将一直保持在非常少的流量下,直到服务器生成要与客户端通信的事件。但请注意,WebSocket 很容易被防火墙和代理击败,并且 Webpush 可能无法实现/支持,在这种情况下,Vaadin 中的 Push 实现(async-io.org 的 Atmosphere Framework 库)可能会退回到轮询技术。

    另一个缺点是每个客户端进行自己的重复轮询并且每个客户端都在服务器端触发单独的执行(例如在数据库中搜索新数据)效率低下。如果您有许多客户端都在使用同一组不可变对象,那么 Push 可以更有效地执行一次搜索新数据并将相同的一组数据对象交付给所有客户端。

    推送

    Vaadin 与 Atmosphere 的结合(以上链接)极大地简化了在您的网络应用程序中使用 Push 技术。不过,与使用轮询功能相比,移动部件更多的情况要复杂一些。

    首先,在 UI 子类上使用 @Push 注释启用 Push。

    然后使用ScheduledExecutorService 安排每分钟触发一次事件。使用ServletContextListener 设置该执行程序。有关所有这些,请参见下面的示例代码。

    优点

    推送在能够使用WebSocket 技术或Webpush 的网络流量方面非常有效,如上所述。

    缺点

    不幸的是,WebSocket 可以被防火墙和代理打败。而且 Webpush 是新的,可能不会被广泛支持。在这种情况下,Vaadin/Atmosphere 可能会退回到使用轮询方法。

    另一个缺点是编码有点复杂。刚接触这项工作的程序员可能需要一段时间才能掌握各种活动部分。

    • 您需要在服务器端有一个后台线程来跟踪时间,在我们的例子中每分钟触发一次。现代的方法是使用ScheduledExecutorService 来处理线程和触发计划。
    • 要设置该执行程序服务,您需要实现ServletContextListener,如下所述。

    请注意,某些推送方法,尤其是 WebSocket,涉及维护开放的网络连接。因此,这会消耗服务器计算机上的端口号等资源。

    示例应用

    我使用 Vaadin 8.6beta1 构建了一个完整的工作示例应用程序。此应用支持两者轮询和推送。不确定您是否会在真正的网络应用程序中混合使用两者,但也许可以。

    访问主文件on my Google Drive。添加到通过 Vaadin Ltd. 提供的 Maven 原型 vaadin-archetype-application 创建的项目中。

    警告:这个例子是在几天内兼职拼凑而成的。所以它可能是也可能不是生产就绪的代码,并且可能会或可能不会显示正确的技术。但希望它能帮助指导新手。

    警告:我不是这个领域的专家。所以把我上面的所有讨论和我的示例代码都用grain-of-salt 写在这里。进行自己的研究和研究。

    此应用允许您通过单选按钮启用和禁用每种方法。您还可以通过单击立即手动刷新按钮强制立即刷新。

    绿色阴影表示自上次刷新后更改的值。

    您可以运行多个窗口。观看它们一起或单独或不全部更新,具体取决于您的单选按钮设置。

    数据库

    这个示例应用程序的主要思想是模拟一个数据库,维护大约十件设备/流程/人员/任何东西的当前状态。每个状态由数字 1-10 标识。每个都有一个包含十个值域的状态,即 1-9。并且每个状态都会记录上次更新的时间。

    这十个状态记录在 Vaadin Grid 小部件中显示为行。

    所有这些数据都记录在关系数据库H2 Database Engine 中。作为演示,我们不需要持久性,因此数据库是在内存中的。后台线程随机更新数据库中的状态行。

    MyDbService.java

    这个数据库服务代码建立我们的内存 H2 数据库,为我们的 Status 定义表,并填充十行。这个类也可以随机更新一些行的值。您可以要求检索代表当前存储值的Status 对象中的List。

    Status.java

    每个状态记录在 Java 中由 Status 类表示,一个简单的 POJO。

    生命周期

    Vaadin 基于Java Servlet 技术。您的 Vaadin 应用程序是一个大型 Servlet 实现。作为一个 servlet,它响应用户 Web 浏览器的传入请求。

    在第一个传入请求之前,我们需要做一些设置工作。一方面,我们需要建立并使用十个状态记录填充该数据库。

    Servlet 规范要求所有web containers 都支持ServletContextListener 接口。如果您编写一个实现该接口的类,并将其声明给 Web 容器,那么它将在第一个请求之前和最后一个请求之后调用。

    在我们的示例中,我们使用该挂钩来建立数据库。我们还设置了一个后台线程,该线程随机更改我们存储的状态记录,以模拟用户的更新或来自提要的新数据。

    上下文监听器

    这是我们的示例ServletContextListener。

    向我们的 Web 容器声明其存在的最简单方法是通过 @WebListener 注释,但您可以在部署场景中根据需要选择其他路由。

    @WebListener
    public class MyServletContextListener implements ServletContextListener {
    …
    

    MyUI.java

    这个 Vaadin Web 应用程序的入口点是我们的 UI、MyUI.java 的子类。它有两个工作:(a) 在屏幕上获取我们的用户界面内容,以及 (b) 将自己注册为 PollListener 以对轮询更新做出反应。

    DataDisplayLayout.java

    这是我们的用户界面内容。这是此示例应用程序的核心。它显示了 Vaadin Grid,其显示将使用新数据进行更新。

    DataDisplayLayoutRefreshManager.java

    该经理监督注册我们希望通过推送更新的DataDisplayLayout 实例的发布-订阅(发布-订阅)模型。

    这里使用一组弱引用来跟踪订阅者。因此订阅的DataDisplayLayout 实例可以优雅地通知他们不再更新的愿望,或者该实例可以简单地超出范围以最终作为订阅者被删除。

    轮询方法不需要这个管理器,因为我们的 UI 子类 (MyUI) 的每个实例都单独轮询服务器。

    mytheme.scss

    Vaadin 网格中表示新值的单元格的绿色是通过CSS 设置的。在 Vaadin 8 中,我们通过编辑隐藏在项目的 webapp 文件夹中的 mytheme.scss 文件来做到这一点。

    这里我们定义样式名称fresh_row。

    @import "../valo/valo.scss";
    
    @mixin mytheme {
      @include valo;
    
      // Insert your own theme rules here
      .v-grid-row.fresh_row > td:nth-child(2) {
        background-color: honeydew;
      }
    }
    

    我们必须通过实现样式生成器将该样式名称分配给我们的 Vaadin Grid 行。

    this.grid.setStyleGenerator( ( StyleGenerator ) o -> {
        Status s = ( Status ) o;
        if ( s.getUpdated().isAfter( this.whenRowLastUpdated ) ) {
            return "fresh_row";
        } else {
            return null;
        }
    } );
    

    【讨论】:

    • 我不确定文档,但 PollListener 在 Vaadin 8 和 Vaadin 10 中仍然是一个概念。在 Vaadin 10 中,我们刚刚简化了 API 以使用通用 ComponentEventListener&lt;PollEvent&gt;类型而不是专用的侦听器接口。
    • @LeifÅstrand 感谢您纠正我。我通过 Answer 修改了地址PollListener 和 Push。我创建了a complete example app 来展示两者的实际效果。如果您愿意研究它,我将不胜感激反馈和更正我使用了错误或不明智的方法。
    • 这是一个很好的例子。我有两个小意见。首先,可以澄清的是,轮询侦听器不需要使用UI.access。描述并没有直接错误,但是在投票听众之后立即引入UI.access 的方式可能会给人这样的印象。我的另一条评论是关于使用WeakHashMap 来避免订阅者的内存泄漏。相反,我建议向UI 实例或实际组件添加一个分离侦听器,然后在那里取消注册。这样,清理工作可以稍早发生。弱引用可能仍然是一个很好的后备。
    • 很好的例子,我认为将你的资源移动到 Github 会很棒,恕我直言。
    【解决方案2】:

    基本上有两种从后台活动更新 Vaadin UI 的方法:轮询和推送。每个都有自己的优点和缺点。

    轮询是技术上更简单的方法。它基于浏览器中的计时器,该计时器定期触发请求。任何挂起的更改都将在对该请求的响应中传递给客户端。此外,您可以添加一个针对每个此类请求运行的侦听器,以便您可以手动检查更改并在需要时更新 UI。

    推送基于保持客户端和服务器之间的持久连接打开,以便服务器可以立即向客户端发送更改,而不必等到客户端打开连接并请求更改。这样做的好处是可以在发生更改时立即将更改发送给客户端,而不是定期发送。

    使用哪个取决于您的要求。轮询可能会使用更少的资源,因为不需要一直保持连接打开。如果数据更改时没有服务器端触发器,轮询也可能是有益的,但相反,服务器端逻辑仍然必须定期显式检查是否有任何更改。推送的主要好处是可以在发生某事时立即发送更改。

    【讨论】:

    • 感谢您提供有关轮询和推送的详细信息。据我了解,我的用例需要 PollListener。但我想知道我可以为视图创建一次 PollListener 并在每次用户更改刷新率时更新 PollInterval。??
    • PollListener 可能更适合您的情况。添加侦听器后,即使轮询间隔发生更改,它也会保持注册状态。
    猜你喜欢
    • 2018-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 2021-08-21
    相关资源
    最近更新 更多