在 Vaadin 7 应用程序中使用推送在多个客户端之间显示相同的数据

2024-01-22

我想向多个客户端共享同一组数据。我需要使用推送来自动更新他们在屏幕上的视图。

我已阅读问题和答案,Vaadin 7 应用程序中推送的最小示例(“@Push”) https://stackoverflow.com/q/27808460/642706。现在我需要一个更强大的现实例子。一方面,我知道在 Servlet 环境中拥有永无休止的线程并不是一个好主意。

我不希望每个用户都有自己的线程,每个用户都自己访问数据库。让一个线程单独检查数据库中的新数据似乎更合乎逻辑。找到后,该线程应将新数据发布到所有等待更新的用户 UI/布局。


完整工作示例

下面您将找到几个类的代码。他们共同制作了一个完整的 Vaadin 7.3.8 应用程序示例,使用新的内置推送功能向任意数量的用户同时发布一组数据。我们通过随机生成一组数据值来模拟检查数据库中的新数据。

当您运行此示例应用程序时,会出现一个窗口,其中显示当前时间以及一个按钮。时间每秒更新一次,共一百次。

本次更新时间为not真实的例子。时间更新器还有另外两个用途:

  • 它的简单代码会检查您的 Vaadin 应用程序、Web 服务器和 Web 浏览器中是否正确配置了 Push。
  • 遵循中给出的示例代码服务器推送部分 https://vaadin.com/book/vaadin7/-/page/advanced.push.html of 瓦丁之书 https://vaadin.com/book。我们这里的时间更新程序几乎完全来自该示例,除了他们更新chart https://vaadin.com/add-ons/charts每分钟,我们都会更新一段文字。

要查看此应用程序的真实示例,请单击/点击“打开数据窗口”按钮。将打开第二个窗口,显示三个文本字段。每个字段都包含一个随机生成的值,我们假设该值来自数据库查询。

这样做需要一些工作,需要几部分。让我们回顾一下这些片段。

Push

在当前版本的 Vaadin 7.3.8 中,无需插件或附加组件即可启用推送技术 https://en.wikipedia.org/wiki/Push_technology。甚至与 Push 相关的 .jar 文件也与 Vaadin 捆绑在一起。

See the 瓦丁之书 https://vaadin.com/book/vaadin7/-/page/advanced.push.html了解详情。但实际上您需要做的就是添加@Push https://vaadin.com/api/com/vaadin/annotations/Push.html您的子类的注释UI https://vaadin.com/api/com/vaadin/ui/UI.html.

使用最新版本的 Servlet 容器和 Web 服务器。推送相对较新,并且实现正在不断发展,特别是对于WebSocket https://en.wikipedia.org/wiki/WebSocket种类。例如,如果使用 Tomcat,请确保使用 Tomcat 7 或 8 的最新更新。

定期检查新数据

我们必须有某种方法来重复查询数据库以获取新数据。

永无止境的线程并不是在 Servlet 环境中执行此操作的最佳方法,因为当 Web 应用程序取消部署或 Servlet 包含关闭时,线程不会结束。 Thread会继续在JVM中运行,浪费资源,导致内存泄漏等问题。

Web 应用程序启动/关闭挂钩

理想情况下,我们希望在 Web 应用程序启动(部署)和 Web 应用程序关闭(或取消部署)时收到通知。当得知这一情况后,我们可以启动或中断该数据库查询线程。幸运的是,每个 Servlet 容器都提供了这样一个钩子。这Servlet 规范 https://www.jcp.org/en/jsr/detail?id=340需要一个容器支持ServletContextListener http://docs.oracle.com/javaee/7/api/javax/servlet/ServletContextListener.html界面。

我们可以编写一个实现这个接口的类。当我们的 Web 应用程序(我们的 Vaadin 应用程序)部署时,我们的监听器类’contextInitialized http://docs.oracle.com/javaee/7/api/javax/servlet/ServletContextListener.html#contextInitialized(javax.servlet.ServletContextEvent)叫做。取消部署时,contextDestroyed http://docs.oracle.com/javaee/7/api/javax/servlet/ServletContextListener.html#contextDestroyed(javax.servlet.ServletContextEvent)方法被调用。

执行人服务

从这个钩子我们可以启动一个线程。但还有更好的方法。 Java 配备了ScheduledExecutorService http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html。该类有一个可供使用的线程池,以避免实例化和启动线程的开销。您可以分配一项或多项任务(Runnable http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) 给执行者,定期运行。

网络应用监听器

这是我们的 Web 应用程序侦听器类,使用 Java 8 中提供的 Lambda 语法。

package com.example.pushvaadinapp;

import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * Reacts to this web app starting/deploying and shutting down.
 *
 * @author Basil Bourque
 */
@WebListener
public class WebAppListener implements ServletContextListener
{

    ScheduledExecutorService scheduledExecutorService;
    ScheduledFuture<?> dataPublishHandle;

    // Constructor.
    public WebAppListener ()
    {
        this.scheduledExecutorService = Executors.newScheduledThreadPool( 7 );
    }

    // Our web app (Vaadin app) is starting up.
    public void contextInitialized ( ServletContextEvent servletContextEvent )
    {
        System.out.println( Instant.now().toString() + " Method WebAppListener::contextInitialized running." );  // DEBUG logging.

        // In this example, we do not need the ServletContex. But FYI, you may find it useful.
        ServletContext ctx = servletContextEvent.getServletContext();
        System.out.println( "Web app context initialized." );   // INFO logging.
        System.out.println( "TRACE Servlet Context Name : " + ctx.getServletContextName() );
        System.out.println( "TRACE Server Info : " + ctx.getServerInfo() );

        // Schedule the periodic publishing of fresh data. Pass an anonymous Runnable using the Lambda syntax of Java 8.
        this.dataPublishHandle = this.scheduledExecutorService.scheduleAtFixedRate( () -> {
            System.out.println( Instant.now().toString() + " Method WebAppListener::contextDestroyed->Runnable running. ------------------------------" ); // DEBUG logging.
            DataPublisher.instance().publishIfReady();
        } , 5 , 5 , TimeUnit.SECONDS );
    }

    // Our web app (Vaadin app) is shutting down.
    public void contextDestroyed ( ServletContextEvent servletContextEvent )
    {
        System.out.println( Instant.now().toString() + " Method WebAppListener::contextDestroyed running." ); // DEBUG logging.

        System.out.println( "Web app context destroyed." );  // INFO logging.
        this.scheduledExecutorService.shutdown();
    }

}

数据出版商

在该代码中,您将看到定期调用 DataPublisher 实例,要求它检查新数据,如果找到则将其传送到所有感兴趣的 Vaadin 布局或小部件。

package com.example.pushvaadinapp;

import java.time.Instant;
import net.engio.mbassy.bus.MBassador;
import net.engio.mbassy.bus.common.DeadMessage;
import net.engio.mbassy.bus.config.BusConfiguration;
import net.engio.mbassy.bus.config.Feature;
import net.engio.mbassy.listener.Handler;

/**
 * A singleton to register objects (mostly user-interface components) interested
 * in being periodically notified with fresh data.
 *
 * Works in tandem with a DataProvider singleton which interacts with database
 * to look for fresh data.
 *
 * These two singletons, DataPublisher & DataProvider, could be combined into
 * one. But for testing, it might be handy to keep them separated.
 *
 * @author Basil Bourque
 */
public class DataPublisher
{

    // Statics
    private static final DataPublisher singleton = new DataPublisher();

    // Member vars.
    private final MBassador<DataEvent> eventBus;

    // Constructor. Private, for simple Singleton pattern.
    private DataPublisher ()
    {
        System.out.println( Instant.now().toString() + " Method DataPublisher::constructor running." );  // DEBUG logging.
        BusConfiguration busConfig = new BusConfiguration();
        busConfig.addFeature( Feature.SyncPubSub.Default() );
        busConfig.addFeature( Feature.AsynchronousHandlerInvocation.Default() );
        busConfig.addFeature( Feature.AsynchronousMessageDispatch.Default() );
        this.eventBus = new MBassador<>( busConfig );
        //this.eventBus = new MBassador<>( BusConfiguration.SyncAsync() );
        //this.eventBus.subscribe( this );
    }

    // Singleton accessor.
    public static DataPublisher instance ()
    {
        System.out.println( Instant.now().toString() + " Method DataPublisher::instance running." );   // DEBUG logging.
        return singleton;
    }

    public void register ( Object subscriber )
    {
        System.out.println( Instant.now().toString() + " Method DataPublisher::register running." );   // DEBUG logging.
        this.eventBus.subscribe( subscriber ); // The event bus is thread-safe. So hopefully we need no concurrency managament here.
    }

    public void deregister ( Object subscriber )
    {
        System.out.println( Instant.now().toString() + " Method DataPublisher::deregister running." );   // DEBUG logging.
        // Would be unnecessary to deregister if the event bus held weak references.
        // But it might be a good practice anyways for subscribers to deregister when appropriate.
        this.eventBus.unsubscribe( subscriber ); // The event bus is thread-safe. So hopefully we need no concurrency managament here.
    }

    public void publishIfReady ()
    {
        System.out.println( Instant.now().toString() + " Method DataPublisher::publishIfReady running." );   // DEBUG logging.

        // We expect this method to be called repeatedly by a ScheduledExecutorService.
        DataProvider dataProvider = DataProvider.instance();
        Boolean isFresh = dataProvider.checkForFreshData();
        if ( isFresh ) {
            DataEvent dataEvent = dataProvider.data();
            if ( dataEvent != null ) {
                System.out.println( Instant.now().toString() + " Method DataPublisher::publishIfReady…post running." );   // DEBUG logging.
                this.eventBus.publishAsync( dataEvent ); // Ideally this would be an asynchronous dispatching to bus subscribers.
            }
        }
    }

    @Handler
    public void deadEventHandler ( DeadMessage event )
    {
        // A dead event is an event posted but had no subscribers.
        // You may want to subscribe to DeadEvent as a debugging tool to see if your event is being dispatched successfully.
        System.out.println( Instant.now() + " DeadMessage on MBassador event bus : " + event );
    }

}

访问数据库

该 DataPublisher 类使用 DataProvider 类来访问数据库。在我们的例子中,我们只是生成随机数据值,而不是实际访问数据库。

package com.example.pushvaadinapp;

import java.time.Instant;
import java.util.Random;
import java.util.UUID;

/**
 * Access database to check for fresh data. If fresh data is found, package for
 * delivery. Actually we generate random data as a way to mock database access.
 *
 * @author Basil Bourque
 */
public class DataProvider
{

    // Statics
    private static final DataProvider singleton = new DataProvider();

    // Member vars.
    private DataEvent cachedDataEvent = null;
    private Instant whenLastChecked = null; // When did we last check for fresh data.

    // Other vars.
    private final Random random = new Random();
    private Integer minimum = Integer.valueOf( 1 ); // Pick a random number between 1 and 999.
    private Integer maximum = Integer.valueOf( 999 );

    // Constructor. Private, for simple Singleton pattern.
    private DataProvider ()
    {
        System.out.println( Instant.now().toString() + " Method DataProvider::constructor running." );   // DEBUG logging.
    }

    // Singleton accessor.
    public static DataProvider instance ()
    {
        System.out.println( Instant.now().toString() + " Method DataProvider::instance running." );   // DEBUG logging.
        return singleton;
    }

    public Boolean checkForFreshData ()
    {
        System.out.println( Instant.now().toString() + " Method DataProvider::checkForFreshData running." );   // DEBUG logging.

        synchronized ( this ) {
            // Record when we last checked for fresh data.
            this.whenLastChecked = Instant.now();

            // Mock database access by generating random data.
            UUID dbUuid = java.util.UUID.randomUUID();
            Number dbNumber = this.random.nextInt( ( this.maximum - this.minimum ) + 1 ) + this.minimum;
            Instant dbUpdated = Instant.now();

            // If we have no previous data (first retrieval from database) OR If the retrieved data is different than previous data --> Fresh.
            Boolean isFreshData = ( ( this.cachedDataEvent == null ) ||  ! this.cachedDataEvent.uuid.equals( dbUuid ) );

            if ( isFreshData ) {
                DataEvent freshDataEvent = new DataEvent( dbUuid , dbNumber , dbUpdated );
                // Post fresh data to event bus.
                this.cachedDataEvent = freshDataEvent; // Remember this fresh data for future comparisons.
            }

            return isFreshData;
        }
    }

    public DataEvent data ()
    {
        System.out.println( Instant.now().toString() + " Method DataProvider::data running." );   // DEBUG logging.

        synchronized ( this ) {
            return this.cachedDataEvent;
        }
    }

}

包装数据

DataProvider 打包新数据以传递给其他对象。我们定义一个 DataEvent 类作为该包。或者,如果您需要提供多组数据或对象而不是单个数据或对象,则可以将 Collection 放入您的 DataHolder 版本中。打包对于想要显示这些新数据的布局或小部件有意义的任何内容。

package com.example.pushvaadinapp;

import java.time.Instant;
import java.util.UUID;

/**
 * Holds data to be published in the UI. In real life, this could be one object
 * or could hold a collection of data objects as might be needed by a chart for
 * example. These objects will be dispatched to subscribers of an MBassador
 * event bus.
 *
 * @author Basil Bourque
 */
public class DataEvent
{

    // Core data values.
    UUID uuid = null;
    Number number = null;
    Instant updated = null;

    // Constructor
    public DataEvent ( UUID uuid , Number number , Instant updated )
    {
        this.uuid = uuid;
        this.number = number;
        this.updated = updated;
    }

    @Override
    public String toString ()
    {
        return "DataEvent{ " + "uuid=" + uuid + " | number=" + number + " | updated=" + updated + " }";
    }

}

分发数据

将新数据打包到 DataEvent 中后,DataProvider 将其交给 DataPublisher。因此,下一步是将数据获取到感兴趣的 Vaadin 布局或小部件以呈现给用户。但是我们如何知道哪些布局/小部件对此数据感兴趣?我们如何将这些数据传递给他们?

一种可能的方法是观察者模式 https://en.wikipedia.org/wiki/Observer_pattern。我们在 Java Swing 和 Vaadin 中看到了这种模式,例如ClickListener https://vaadin.com/api/com/vaadin/ui/Button.ClickListener.html for a Button https://vaadin.com/api/com/vaadin/ui/Button.html在瓦丁。这种模式意味着观察者和被观察者彼此了解。这意味着在定义和实现接口方面需要做更多的工作。

活动总线

在我们的例子中,我们不需要数据的生产者(DataPublisher)和消费者(Vaadin 布局/小部件)相互了解。所有小部件想要的只是数据,而不需要与生产者进行进一步的交互。因此我们可以使用不同的方法,即事件总线。在事件总线中,当发生有趣的事情时,某些对象会发布“事件”对象。当事件对象被发布到总线时,其他对象注册了它们希望得到通知的兴趣。发布后,总线通过调用特定方法并传递事件来将该事件发布给所有注册的订阅者。在我们的例子中,将传递 DataEvent 对象。

但是注册的订阅对象上的哪个方法将被调用呢?通过 Java 注释、反射和内省技术的魔力,任何方法都可以被标记为要调用的方法。只需用注释标记所需的方法,然后让总线在发布事件时在运行时找到该方法。

无需自己构建任何此事件总线。在 Java 世界中,我们可以选择事件总线实现。

谷歌番石榴EventBus

最知名的可能是 Google GuavaEventBus https://code.google.com/p/guava-libraries/wiki/EventBusExplained. 谷歌番石榴 https://github.com/google/guava是 Google 内部开发的一系列各种实用项目,然后开源供其他人使用。 EventBus 包就是这些项目之一。我们可以使用 Guava EventBus。事实上,我最初确实使用这个库构建了这个示例。但 Guava EventBus 有一个限制:它拥有强引用。

弱引用

当对象注册其对被通知的兴趣时,任何事件总线都必须通过保存对注册对象的引用来保留这些订阅的列表。理想情况下,这应该是一个弱引用 https://en.wikipedia.org/wiki/Weak_reference,这意味着订阅对象应该达到其用处并成为候选者垃圾收集 https://en.wikipedia.org/wiki/Garbage_collection_(computer_science),该对象可能会这样做。如果事件总线持有强引用,则对象无法继续进行垃圾回收。弱引用告诉 JVM 我们不这样做really关心对象,我们关心一点但不足以坚持保留该对象。使用弱引用时,事件总线会在尝试向订阅者通知新事件之前检查是否存在空引用。如果为 null,则事件总线可以将该槽删除到其对象跟踪集合中。

您可能认为,作为持有强引用问题的解决方法,您可以让注册的 Vaadin 小部件覆盖detach方法。当 Vaadin 小部件不再使用时,您会收到通知,然后您的方法将从事件总线中取消注册。如果将订阅对象从事件总线中取出,则不再有强引用,也不再有问题。但就像Java对象方法一样finalize并不总是被称为 https://stackoverflow.com/q/2506488/642706,瓦丁也是如此detach方法并不总是被调用。请参阅上的帖子这个线程 https://vaadin.com/forum#!/thread/1409950作者:Vaadin 专家亨利·萨拉 https://vaadin.com/web/hesara/home了解详情。依靠detach可能会导致内存泄漏和其他问题。

Massador 活动巴士

See 我的博文 http://crafted-software.blogspot.com/2015/01/event-bus-for-java.html有关事件总线库的各种 Java 实现的讨论。我选择的那些大使 https://github.com/bennidi/mbassador用于本示例应用程序。它是存在的理由就是弱引用的使用。

用户界面类

线程之间

要实际更新 Vaadin 布局和小部件的值,有一个大问题。这些小部件在它们自己的用户界面处理线程(该用户的主 Servlet 线程)中运行。同时,数据库检查、数据发布和事件总线调度都发生在由执行器服务管理的后台线程上。切勿从单独的线程访问或更新 Vaadin 小部件!这条规则绝对至关重要。更棘手的是,这样做实际上可能在开发过程中起作用。但如果你在生产中这样做,你将会陷入痛苦的境地。

那么我们如何从后台线程获取数据并传递到主 Servlet 线程中运行的小部件中呢?这UI https://vaadin.com/api/com/vaadin/ui/UI.html类提供了一个专门用于此目的的方法:access https://vaadin.com/api/com/vaadin/ui/UI.html#access(java.lang.Runnable)。你通过一个Runnable https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html to the access方法,Vaadin 安排该 Runnable 在主用户界面线程上执行。十分简单。

剩余课程

为了总结这个示例应用程序,这里是其余的类。 “MyUI”类替换由创建的默认项目中的同名文件Vaadin 7.3.7 的新 Maven 原型 https://vaadin.com/blog/-/blogs/vaadin-7-3-7-and-new-maven-archetypes.

package com.example.pushvaadinapp;

import com.vaadin.annotations.Push;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.annotations.Widgetset;
import com.vaadin.server.BrowserWindowOpener;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import java.time.Instant;
import javax.servlet.annotation.WebServlet;

/**
 * © 2014 Basil Bourque. This source code may be used freely forever by anyone
 * absolving me of any and all responsibility.
 */
@Push
@Theme ( "mytheme" )
@Widgetset ( "com.example.pushvaadinapp.MyAppWidgetset" )
public class MyUI extends UI
{

    Label label = new Label( "Now : " );
    Button button = null;

    @Override
    protected void init ( VaadinRequest vaadinRequest )
    {
        // Prepare widgets.
        this.button = this.makeOpenWindowButton();

        // Arrange widgets in a layout.
        VerticalLayout layout = new VerticalLayout();
        layout.setMargin( Boolean.TRUE );
        layout.setSpacing( Boolean.TRUE );
        layout.addComponent( this.label );
        layout.addComponent( this.button );

        // Put layout in this UI.
        setContent( layout );

        // Start the data feed thread
        new FeederThread().start();
    }

    @WebServlet ( urlPatterns = "/*" , name = "MyUIServlet" , asyncSupported = true )
    @VaadinServletConfiguration ( ui = MyUI.class , productionMode = false )
    public static class MyUIServlet extends VaadinServlet
    {
    }

    public void tellTime ()
    {
        label.setValue( "Now : " + Instant.now().toString() ); // If before Java 8, use: new java.util.Date(). Or better, Joda-Time.
    }

    class FeederThread extends Thread
    {

        // This Thread class is merely a simple test to verify that Push works.
        // This Thread class is not the intended example.
        // A ScheduledExecutorService is in WebAppListener class is the intended example.
        int count = 0;

        @Override
        public void run ()
        {
            try {
                // Update the data for a while
                while ( count < 100 ) {
                    Thread.sleep( 1000 );

                    access( new Runnable() // Special 'access' method on UI object, for inter-thread communication.
                    {
                        @Override
                        public void run ()
                        {
                            count ++;
                            tellTime();
                        }
                    } );
                }

                // Inform that we have stopped running
                access( new Runnable()
                {
                    @Override
                    public void run ()
                    {
                        label.setValue( "Done. No more telling time." );
                    }
                } );
            } catch ( InterruptedException e ) {
                e.printStackTrace();
            }
        }
    }

    Button makeOpenWindowButton ()
    {
        // Create a button that opens a new browser window.
        BrowserWindowOpener opener = new BrowserWindowOpener( DataUI.class );
        opener.setFeatures( "height=300,width=440,resizable=yes,scrollbars=no" );

        // Attach it to a button
        Button button = new Button( "Open data window" );
        opener.extend( button );

        return button;
    }
}

“DataUI”和“DataLayout”完成了此示例 Vaadin 应用程序中的 7 个 .java 文件。

package com.example.pushvaadinapp;

import com.vaadin.annotations.Push;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Widgetset;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;
import java.time.Instant;
import net.engio.mbassy.listener.Handler;

@Push
@Theme ( "mytheme" )
@Widgetset ( "com.example.pushvaadinapp.MyAppWidgetset" )
public class DataUI extends UI
{

    // Member vars.
    DataLayout layout;

    @Override
    protected void init ( VaadinRequest request )
    {
        System.out.println( Instant.now().toString() + " Method DataUI::init running." );   // DEBUG logging.

        // Initialize window.
        this.getPage().setTitle( "Database Display" );
        // Content.
        this.layout = new DataLayout();
        this.setContent( this.layout );

        DataPublisher.instance().register( this ); // Sign-up for notification of fresh data delivery.
    }

    @Handler
    public void update ( DataEvent event )
    {
        System.out.println( Instant.now().toString() + " Method DataUI::update (@Subscribe) running." );   // DEBUG logging.

        // We expect to be given a DataEvent item.
        // In a real app, we might need to retrieve data (such as a Collection) from within this event object.
        this.access( () -> {
            this.layout.update( event ); // Crucial that go through the UI:access method when updating the user interface (widgets) from another thread.
        } );
    }

}

…and…

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.example.pushvaadinapp;

import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import java.time.Instant;

/**
 *
 * @author brainydeveloper
 */
public class DataLayout extends VerticalLayout
{

    TextField uuidField;
    TextField numericField;
    TextField updatedField;
    TextField whenCheckedField;

    // Constructor
    public DataLayout ()
    {
        System.out.println( Instant.now().toString() + " Method DataLayout::constructor running." );   // DEBUG logging.

        // Configure layout.
        this.setMargin( Boolean.TRUE );
        this.setSpacing( Boolean.TRUE );

        // Prepare widgets.
        this.uuidField = new TextField( "UUID : " );
        this.uuidField.setWidth( 22 , Unit.EM );
        this.uuidField.setReadOnly( true );

        this.numericField = new TextField( "Number : " );
        this.numericField.setWidth( 22 , Unit.EM );
        this.numericField.setReadOnly( true );

        this.updatedField = new TextField( "Updated : " );
        this.updatedField.setValue( "<Content will update automatically>" );
        this.updatedField.setWidth( 22 , Unit.EM );
        this.updatedField.setReadOnly( true );

        // Arrange widgets.
        this.addComponent( this.uuidField );
        this.addComponent( this.numericField );
        this.addComponent( this.updatedField );
    }

    public void update ( DataEvent dataHolder )
    {
        System.out.println( Instant.now().toString() + " Method DataLayout::update (via @Subscribe on UI) running." );   // DEBUG logging.

        // Stuff data values into fields. For simplicity in this example app, using String directly rather than Vaadin converters.
        this.uuidField.setReadOnly( false );
        this.uuidField.setValue( dataHolder.uuid.toString() );
        this.uuidField.setReadOnly( true );

        this.numericField.setReadOnly( false );
        this.numericField.setValue( dataHolder.number.toString() );
        this.numericField.setReadOnly( true );

        this.updatedField.setReadOnly( false );
        this.updatedField.setValue( dataHolder.updated.toString() );
        this.updatedField.setReadOnly( true );
    }

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Vaadin 7 应用程序中使用推送在多个客户端之间显示相同的数据 的相关文章

  • Java - 因内存不足错误而关闭

    关于如何最好地处理这个问题 我听到了非常矛盾的事情 并且陷入了以下困境 OOME 会导致一个线程崩溃 但不会导致整个应用程序崩溃 我需要关闭整个应用程序 但不能 因为线程没有剩余内存 我一直认为最佳实践是让它们离开 这样 JVM 就会死掉
  • 如何从Firebase Firestore实时更新文档中获取修改后的字段或数据? [复制]

    这个问题在这里已经有答案了 我有多个文档 我的问题是我无法获取修改的特定数据 我正在获取完整的文档 db collection employees whereEqualTo OID OID addSnapshotListener new E
  • 如何使用Spring WebClient进行同步调用?

    Spring Framework in 休息模板 https docs spring io spring framework docs current javadoc api org springframework web client R
  • 如何强制jar使用(或jar运行的jvm)utf-8而不是系统的默认编码

    我的Windows默认编码是GBK 而我的Eclipse完全是utf 8编码 因此 在我的 Eclipse 中运行良好的应用程序崩溃了 因为导出为 jar 文件时这些单词变得不可读 我必须在 bat 文件中写入以下行才能运行该应用程序 st
  • 将SQL数据引入jquery availabletag

    我正在尝试制作自动完成文本框 但如何将 SQL 数据包含到 jquery 可用标记并循环它 我无法根据以下代码执行该功能 任何帮助 将不胜感激 谢谢 这是我的预期输出 预期结果演示 http jsfiddle net VvETA 71 jq
  • 删除优先级队列的尾部元素

    如何删除优先级队列的尾部元素 我正在尝试使用优先级队列实现波束搜索 一旦优先级队列已满 我想删除最后一个元素 优先级最低的元素 Thanks 没有简单的方法 将元素从原始元素复制到新元素 最后一个除外 PriorityQueue remov
  • 埃拉托色尼筛法 - 实现返回一些非质数值?

    我用 Java 实现了埃拉托斯特尼筛法 通过伪代码 public static void sieveofEratosthenes int n boolean numArray numArray new boolean n for int i
  • 您建议使用哪种压缩(GZIP 是最流行的)servlet 过滤器?

    我正在寻找一个用于大容量网络应用程序的 GZIP servlet 过滤器 我不想使用容器特定的选项 要求 能够压缩响应负载 XML Faster 已在大批量应用的生产中得到验证 应适当设置适当内容编码 跨容器移植 可选择解压缩请求 谢谢 我
  • Spring数据中的本机查询连接

    我有课 Entity public class User Id Long id String name ManyToMany List
  • Android蓝牙java.io.IOException:bt套接字已关闭,读取返回:-1

    我正在尝试编写一个代码 仅连接到运行 Android 5 0 KitKat 的设备上的 目前 唯一配对的设备 无论我尝试了多少方法 我仍然会收到此错误 这是我尝试过的最后一个代码 它似乎完成了我看到人们报告为成功的所有事情 有人能指出我做错
  • 如何使用正则表达式验证 1-99 范围?

    我需要验证一些用户输入 以确保输入的数字在 1 99 范围内 含 这些必须是整数 Integer 值 允许前面加 0 但可选 有效值 1 01 10 99 09 无效值 0 007 100 10 5 010 到目前为止 我已经制定了以下正则
  • 虽然我的类已加载,但 Class.forName 抛出 ClassNotFoundException

    代码如下 它的作用是加载我放在主目录中的 jar 文件中的所有类 import java io File import java util jar JarFile import java util jar JarEntry import j
  • Java:如何确定文件所在的驱动器类型?

    Java 是否有一种独立于平台的方法来检测文件所在的驱动器类型 基本上我有兴趣区分 硬盘 可移动驱动器 如 USB 记忆棒 和网络共享 JNI JNA 解决方案不会有帮助 可以假设 Java 7 您可以使用 Java 执行 cmd fsut
  • 在 Clojure 中解压缩 zlib 流

    我有一个二进制文件 其内容由zlib compress在Python上 有没有一种简单的方法可以在Clojure中打开和解压缩它 import zlib import json with open data json zlib wb as
  • Play.application() 的替代方案是什么

    我是 Play 框架的新手 我想读取conf文件夹中的一个文件 所以我用了Play application classloader getResources Data json nextElement getFile 但我知道 play P
  • 使用Java绘制维恩图

    我正在尝试根据给定的布尔方程绘制维恩图 例如 a AND b AND c我想在 Android 手机上执行此操作 因此我需要找到一种使用 Java 来执行此操作的方法 我找到了一个完美的小部件 它可以完成我在这方面寻找的一切布尔代数计算器
  • 替换文件中的字符串

    我正在寻找一种方法来替换文件中的字符串而不将整个文件读入内存 通常我会使用 Reader 和 Writer 即如下所示 public static void replace String oldstring String newstring
  • 源值 1.5 的错误已过时,将在未来版本中删除

    我使用 scala maven plugin 来编译包含 scala 和 java 代码的项目 我已经将源和目标设置为1 7 但不知道为什么maven仍然使用1 5 这是我在 pom xml 中的插件
  • 记录类名、方法名和行号的性能影响

    我正在我的 java 应用程序中实现日志记录 以便我可以调试应用程序投入生产后可能出现的潜在问题 考虑到在这种情况下 人们不会奢侈地使用 IDE 开发工具 以调试模式运行事物或单步执行完整代码 因此在每条消息中记录类名 方法名和行号将非常有
  • 检查应用程序是否在 Android Market 上可用

    给定 Android 应用程序 ID 包名称 如何以编程方式检查该应用程序是否在 Android Market 上可用 例如 com rovio angrybirds 可用 而 com random app ibuilt 不可用 我计划从

随机推荐

  • 字符串如何存储在 VBA 字典结构中?

    因为我目前正在演奏大量的字符串 看看另一个问题 数组和Arraylist的VBA内存大小 https stackoverflow com questions 20526324 vba memory size of arrays and ar
  • “热门”哈希键在 Amazon DynamoDB 上的实践中会如何影响整个过程?

    首先 这是一个支持document http docs aws amazon com amazondynamodb latest developerguide GuidelinesForTables html为 DynamoDB 提供有关如
  • JSON.stringify 与序列化

    Is JSON stringify 相当于序列化或有效序列化 或者它只是实现序列化的必要步骤 序列化 换句话说 就是JSON stringify 对于序列化来说足够但不是必需的 或者是必要但不充分 或者说它对于 JavaScript 对象的
  • 单元测试:初学者问题

    我终于开始进行单元测试了 因为我知道我应该这样做一段时间 但我有几个问题 我应该或不应该重新测试父母 测试孩子们是否在课堂上 没有方法被覆盖 从概念上讲 您如何测试 提交了表格的一部分 我在用着 PHP Edit 我问这个问题的原因是我有一
  • C# 中用于关闭 Windows 窗体窗体的转义按钮

    我已经尝试过以下方法 private void Form1 KeyDown object sender System Windows Forms KeyEventArgs e if Keys e KeyValue Keys Escape t
  • 如何防止 jquery ajax 对数据参数上的某些字符进行编码?

    我正在使用 jquery ajax 从三方 Web 服务查询数据 问题是我需要传递冒号字符 作为数据的一部分 不对其进行编码 但 ajax 方法会自动对所有非字母字符进行编码 所以问题是如何防止 jquery ajax 对数据参数上的某些字
  • Numpy 赋值,如“numpy.take”

    是否可以按照 take 功能的工作方式分配给 numpy 数组 例如 如果我有一个数组a 索引列表inds和所需的轴 我可以使用 take 如下 import numpy as np a np arange 12 reshape 3 1 i
  • TypeScript 属性装饰器:访问其他属性

    我有一个这样的类点 class Point test admin x number 6 y number 5 使用测试装饰器 function test myValue string function t target Object pro
  • T-SQL 列表表、列

    在 T SQL SQL Server 2000 中 如何列出数据库中的所有表和列 此外 在单独的查询中 有一种方法可以列出所有列以及数据类型和约束 NULL 等 谢谢 请查看信息图式 http learn microsoft com en
  • 如何使用 Objective C 将应用程序分配到 Mac OS X Lion 的所有桌面(空间)?

    我正在尝试在 Mac OS X Lion 上创建一个应用程序 该应用程序需要将应用程序分配给所有桌面 空间 这可以通过右键单击应用程序的停靠栏图标并选择来手动完成选项 gt 分配到 gt 所有桌面 但是 我需要找到一种通过 Objectiv
  • Bootstrap(3.1.1) 字形在 Firefox 中不起作用

    我偶然发现了 Bootstrap Glyphicons 无法与 Firefox 配合使用但与其他浏览器配合正常工作的问题 Q How to make Glyphicons from Bootstrap 3 1 1 work with fir
  • 启用 GlassFish 压缩

    如何启用玻璃鱼压缩 我在 http lister 属性中启用了压缩 但没有改变回应 登录管理控制台 localhost 4848 前往Network Config gt Network Listener 选择要启用 gzip 的侦听器 gt
  • Arduino Nano 上的 WiFi

    我无法找到的虚拟问题 我用来将 WiFi 802 11b g n 添加到 Raspberry Pi 的扩展板也可以在 Nano 上使用吗 换句话说 向 Arduino Nano 板添加 WiFi 有多容易 可行 Thanks Arduino
  • 设置 xcode 项目的默认字体样式、颜色和大小

    我正在开发的应用程序有一个特定的配色方案 所以我想知道是否可以设置 默认 背景颜色 文本颜色和字体大小 以便每次我在界面中创建新视图或标签时Builder 我不必更改所有这些参数 从 iOS 5 开始 有UIAppearance所有标准 U
  • useLocation 挂钩即使在硬刷新时也能保持状态

    在做一个项目时 我注意到一个奇怪的行为useLocation我找不到解释的钩子 我有一个按钮 单击它会将您重定向到EditOrder页面并将传递一个状态 const navigate useNavigate const handleClic
  • Django Websockets 数据发送到错误的套接字

    使用 Django Websockets Channels 我创建了一个 一个 组 并且来回消息工作得很好 我们称之为A组 当我在不同的浏览器中打开第二个组和第二个 我们称之为 B 组 WebSocket 连接时 问题就开始了 我尝试发送到
  • 在运行时更改 Angular 中的区域设置?

    我正在做一些关于在运行时更改 Angular 区域设置的研究 并发现了这个线程 如何在 Angular 2 的 DatePipe 中设置区域设置 https stackoverflow com questions 34904683 how
  • 将 POS 标签从 TextBlob 转换为 Wordnet 兼容输入

    我使用 Python 和 nltk Textblob 进行一些文本分析 有趣的是 您可以为 wordnet 添加 POS 以使同义词搜索更加具体 但不幸的是 nltk 和 Textblob 中的标记与 wordnet 对其同义词集类期望的输
  • 如何检索商店状态的旧值和新值

    在我的组件中 我试图获取在 vuex 存储状态中分配的特定对象数组的旧值和新值 如下所示 但是 当我 newArray 和 oldArray 返回相同的对象数组时 我从以下文档中了解了以下内容 但我不明白检索不同版本的最佳方法是什么 注意
  • 在 Vaadin 7 应用程序中使用推送在多个客户端之间显示相同的数据

    我想向多个客户端共享同一组数据 我需要使用推送来自动更新他们在屏幕上的视图 我已阅读问题和答案 Vaadin 7 应用程序中推送的最小示例 Push https stackoverflow com q 27808460 642706 现在我