当前位置: 首页 > 知识库问答 >
问题:

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

越文康
2023-03-14

我已经阅读了问题和答案,Vaadin7应用程序中推送的最小示例(“@Push”)。现在我需要一个更健壮的现实例子。首先,我知道在Servlet环境中拥有一个永无止境的线程不是一个好主意。

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

共有1个答案

沙柏
2023-03-14

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

当您运行这个示例应用程序时,会出现一个窗口,显示当前时间和一个按钮。时间每秒更新一次,每次更新一百次。

这种时间更新不是真正的例子。时间更新器还有两个目的:

    null

详见瓦丁之书。但实际上,您所需要做的就是将@push注释添加到您的UI子类中。

使用Servlet容器和web服务器的最新版本。Push是相对较新的,实现也在不断发展,尤其是对于WebSocket品种。例如,如果使用Tomcat,请确保使用Tomcat7或8的最新更新。

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

下面是我们的web应用程序侦听器类,使用Java8中可用的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 html" target="_blank">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;
        }
    }

}
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 + " }";
    }

}

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

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

最著名的可能是Google Guava EventBus。谷歌番石榴是谷歌内部开发的一系列各种实用项目,然后开源给其他人使用。EventBus包就是其中一个项目。我们可以用番石榴酒。实际上,我最初确实使用这个库构建了这个示例。但是Guava EventBus有一个限制:它拥有很强的引用。

那么,我们如何从后台线程获得数据,并将其传递到运行在主Servlet线程中的小部件中呢?UI类为此提供了一个方法:access。您将一个Runnable传递给Access方法,Vaadin将调度Runnable在主用户界面线程上执行。容易的。

为了总结这个示例应用程序,下面是剩下的类。“myui”类替换了由Vaadin 7.3.7的新Maven原型创建的默认项目中的同名文件。

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.
        } );
    }

}
/*
 * 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 );
    }

}
 类似资料:
  • 此链接正在解释与和。但没有解释同样的路线问题。所以我想问我的问题。 我用创建了react项目,并在文件夹中创建了服务器。我想在。所以我写了这样的代码。 公共/index.html src/服务器/server.js package.json 我测试, -- localhost:4000/代码 我想只是一个静态文件,每当。为什么不显示

  • 我正在将当前的应用程序迁移到多租户体系结构。由于只有一个代码库,我需要解决多个租户的问题。我使用的是单数据库、多模式的方法。每个租户将被分配一个单独的模式,其中元数据保存在默认模式中。 应用程序是用ASP构建的。NET MVC。我使用Dapper连接到我的SQL Server。我有50个函数,使用直接查询和存储过程调用数据库。当为每个租户初始化dapper时,是否有任何方法可以在不改变函数的情况下

  • 首先,我在我的iOS原生应用程序中集成了Google Drive,并使用GTMOAuth2进行授权登录,其中包含一个客户端ID。 然后,我现在尝试集成firebase身份验证,并在GoogleService-info.plist中获得了一个新的客户机id。

  • 问题内容: 我对React还是很陌生,我正在开发一个应用程序,它将获取网页的实际屏幕截图,并且该应用程序可以在所截取的屏幕截图之上绘制并添加涂鸦。最初,我使用html2canvas和domToImage拍摄客户端屏幕快照,但它无法完全呈现网页中显示的图像。 Reddit用户/ pamblam0建议我调查Google的Puppeteer。它的工作方式是启动无头铬浏览器,该浏览器转到我在本地主机上的r

  • 我正在使用firebase auth的电子邮件和密码登录。 现在,当我更改帐户时,显示的用户名将根据更改的帐户而更改。我想在不更改用户名的情况下发布多个帐户。 这是用于声明名称的代码: 注册代码: 邮政编码:

  • 我正在用spring boot微服务配置Keycloak SSO。我希望多个keycloak客户端访问Spring Boot服务。如果在Spring Boot应用程序中使用Keycloak适配器,则required属性只支持一个客户机和secret。如何在运行时在spring boot app中添加多个客户端? 我在中使用了以下适配器 以下是在中配置的 动态客户机注册可用于spring boot