@SpringUI
public class VaadinUI extends UI {
...
String sql = "SELECT * FROM table1";
button.addClickListener(e -> layout.addComponent(new Label(service.evalSql(sql))));
...
目前,当按下按钮时,页面会等待evalSql()在添加新Label之前从数据库中返回结果。
如何改变这一点,当按下按钮时,会立即添加一个新的Label,设置为初始占位符字符串(“获取结果..”),但在数据库返回后更新为结果字符串?
答案 0 :(得分:3)
您需要在UI中使用@Push,并在使用UI.access(..)完成查询时更新Label的内容。这里有一些很好的文档,有一些例子:
https://vaadin.com/docs/v8/framework/advanced/advanced-push.html
答案 1 :(得分:2)
好消息是你想要的,在你的Vaadin用户界面中有一个小部件,稍后通过在服务器后台完成的工作进行更新,而不会阻止用户界面对用户的响应,可以完成。使用Vaadin及其基于Java的后端可以很好地完成它。
坏消息是,如果您不熟悉并发和线程,那么您就有了学习曲线。
希望您的应用在后台执行某些操作并稍后无阻塞地进行检查的技术术语是:asynchronous更新。
我们可以使用线程在Java中完成此任务。生成一个线程来运行您的SQL服务代码。当该代码完成数据库工作时,该代码通过调用UI::access(Runnable runnable)
发布请求以使原始用户界面(UI)线程更新Label
小部件。
正如Label
小部件的Answer by Lund更新中所讨论的,需要Push Technology从服务器端生成的事件更新浏览器。幸运的是,Vaadin 8 and later has excellent support for Push并使您的应用程序中的Push非常容易。
提示:普遍推进,尤其是WebSocket,近年来发展很快。使用最新一代的Servlet容器将改善您的体验。例如,如果使用Tomcat,我建议使用最新版本的Tomcat 8.5或9.
Java对线程有很好的支持。许多必要的工作都是由Java内置的Executor框架为您处理的。
如果您不熟悉线程,那么您将面临一些认真的学习。首先研究Oracle Tutorial on concurrency。最后,您需要阅读并重新阅读Brian Goetz等人的优秀书籍Java Concurrency in Practice几次。
ServletContextListener
您可能希望在Vaadin应用程序启动和退出时设置并拆除您的线程杂耍执行程序服务。这样做的方法是编写一个与Vaadin servlet类分开的类。该类必须实现ServletContextListener
。您可以通过实现两个必需的方法并使用@WebListener
注释类来轻松完成此操作。
务必拆除遗嘱执行人服务。否则,它管理的后台线程可能会在你的Vaadin应用程序关闭后继续存在,甚至关闭你的web container(Tomcat,Jetty等),继续无限期地运行。
这项工作的一个关键想法:永远不要直接从任何背景访问任何Vaadin UI小部件。不要在后台线程中运行的代码中的任何窗口小部件上调用UI窗口小部件上的任何方法,也不要访问任何值。 UI小部件不线程安全(使用户界面技术的线程安全非常困难)。你可能逃脱这样的后台调用,或者在运行时可能会发生可怕的事情。
如果您碰巧使用的是完整的Jakarta EE(以前称为Java EE)服务器而不是Web容器(例如Tomcat或Jetty)或Web Profile服务器(例如TomEE),那么工作上面讨论了执行程序服务,ServletContextListener
已经完成了。使用Java EE 7及更高版本中定义的功能:JSR 236: Concurrency Utilities for JavaTM EE
您的问题已标记为 Spring 。 Spring可能具有帮助完成这项工作的功能。我不知道,因为我不是Spring用户。也许是Spring TaskExecutor?
如果您搜索Stack Overflow,您会发现所有这些主题都已得到解决。
我已经发布了两个完整的示例应用程序,用Push演示Vaadin:
从由Vaadin Ltd.公司提供的Maven原型vaadin-archetype-application
生成的Vaadin 8.4.3应用开始。
package com.basilbourque.example;
import javax.servlet.annotation.WebServlet;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
/**
* This UI is the application entry point. A UI may either represent a browser window
* (or tab) or some part of an HTML page where a Vaadin application is embedded.
* <p>
* The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
* overridden to add component to the user interface and initialize non-component functionality.
*/
@Theme ( "mytheme" )
public class MyUI extends UI {
@Override
protected void init ( VaadinRequest vaadinRequest ) {
final VerticalLayout layout = new VerticalLayout();
final TextField name = new TextField();
name.setCaption( "Type your name here:" );
Button button = new Button( "Click Me" );
button.addClickListener( e -> {
layout.addComponent( new Label( "Thanks " + name.getValue() + ", it works!" ) );
} );
layout.addComponents( name , button );
setContent( layout );
}
@WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
@VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
public static class MyUIServlet extends VaadinServlet {
}
}
如上所述,我们需要将您的SQL服务工作分配为在后台线程上完成的任务。 Java 5及更高版本中的 Executor 框架为这样的线程工作做了所有繁重的工作。我们需要建立一个由线程池支持的执行器服务来对所有用户的Web浏览器窗口中添加的所有新标签进行所有更新。问题是我们在哪里设置,存储和拆除执行程序服务对象?
我们希望执行程序服务可用于我们的Web应用程序的整个生命周期。在第一个用户请求到达我们新推出的Web应用程序之前,我们想要设置执行程序服务。当我们试图关闭我们的Web应用程序时,我们需要拆除该执行程序服务,以便终止其备用线程池中的线程。我们如何与Vaadin网络应用程序的生命周期相结合?
嗯,Vaadin是Java Servlet的一个实现,虽然是一个非常大而复杂的servlet。在Servlet术语中,您的Web应用程序称为“上下文”。 Servlet规范要求所有Servlet containers(例如Tomcat,Jetty等)都注意到任何标记为某些事件的监听器的类。为了利用这一点,我们必须在我们的Vaadin应用程序中添加另一个类,这是一个实现ServletContextListener
接口的类。
如果我们将新类注释为@WebListener
,Servlet容器会注意到这个类,启动我们的Web应用程序时将实例化我们的侦听器对象,然后在适当的时候调用它的方法。在正确初始化servlet之后但在处理任何传入的Web浏览器请求之前调用contextInitialized
方法。在最后一个响应被发送回用户之后,在处理完最后一个Web浏览器请求之后调用contextDestroyed
方法。
因此,实现ServletContextListener
的类是使用其支持线程池设置和拆除执行程序服务的理想之地。
还有一个问题:在设置我们的执行程序服务之后,当用户添加Label
个对象时,我们在哪里存储要在以后的Vaadin servlet中找到并使用的引用?一种解决方案是将执行程序服务引用存储为“上下文”(我们的Web应用程序)中的“属性”。 Servlet规范要求每个Servlet容器为每个上下文(Web应用程序)提供一个简单的键值集合,其中键是String
对象,值是Object
对象。我们可以创建一些字符串来标识我们的执行器服务,然后我们的Vaadin servlet可以稍后执行循环来检索执行程序服务。
具有讽刺意味的是,上面的讨论比实际代码要长!
package com.basilbourque.example;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@WebListener
// Annotate to instruct your web container to notice this class as a context listener and then automatically instantiate as context (your web app) lanuches.
public class MyServletContextListener implements ServletContextListener {
static final public String executorServiceNameForUpdatingLabelAfterSqlService = "ExecutorService for SQL service update of labels";
@Override
public void contextInitialized ( final ServletContextEvent sce ) {
// Initialize an executor service. Store a reference as a context attribute for use later in our webapp’s Vaadin servlet.
ExecutorService executorService = Executors.newFixedThreadPool( 7 ); // Choose an implementation and number of threads appropriate to demands of your app and capabilities of your deployment server.
sce.getServletContext().setAttribute( MyServletContextListener.executorServiceNameForUpdatingLabelAfterSqlService , executorService );
}
@Override
public void contextDestroyed ( final ServletContextEvent sce ) {
// Always shutdown your ExecutorService, otherwise the threads may survive shutdown of your web app and perhaps even your web container.
// The context addribute is stored as `Object`. Cast to `ExecutorService`.
ExecutorService executorService = ( ExecutorService ) sce.getServletContext().getAttribute( MyServletContextListener.executorServiceNameForUpdatingLabelAfterSqlService );
if ( null != executorService ) {
executorService.shutdown();
}
}
}
现在,回到我们的Vaadin应用程序。修改该文件:
@Push
注释Servlet,以使Vaadin能够让服务器端生成的事件更新用户界面小部件。Label
的创建。
Label
的初始文字以包含&#34;已创建:&#34;与当前日期时间。Label
之后,我们从上下文属性集合中检索我们的执行器服务,并向其提交最终将运行以执行我们的SQL服务的Runnable
。为了模拟该SQL服务的工作,我们在半分钟内将后台线程随机秒数休眠。醒来后,该后台线程会询问我们的UI
对象,该对象代表我们在Web浏览器中显示的Web应用程序内容,以便安排另一个Runnable
最终在其主用户界面线程上运行。如上所述,永远不要从后台线程直接访问UI小部件!始终礼貌地要求UI
对象在其自己的线程中根据自己的时间表安排与小部件相关的工作。 如果您不熟悉线程和并发,这可能会令人生畏。研究这段代码,有一段时间沉入其中。你可以用其他方式构建它,但我想在教学目的这里简单一点。不是关注代码的结构/安排,而是关注:
Runnable
)。Runnable
)以代表我们执行与小部件相关的工作(我们的Label
文本更新)。Runnable
来实际修改{{1}的文本那是在不久前添加的。这是我们修改过的Vaadin应用程序。
Label
执行此类异步线程工作时,无法预测执行的确切顺序。您不确切知道后台线程何时以及以何种顺序执行。您不知道package com.basilbourque.example;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebServlet;
import com.vaadin.annotations.Push;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
* This UI is the application entry point. A UI may either represent a browser window
* (or tab) or some part of an HTML page where a Vaadin application is embedded.
* <p>
* The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
* overridden to add component to the user interface and initialize non-component functionality.
*/
@Push // This annotation enables the Push Technology built into Vaadin 8.4.
@Theme ( "mytheme" )
public class MyUI extends UI {
@Override
protected void init ( VaadinRequest vaadinRequest ) {
final VerticalLayout layout = new VerticalLayout();
final TextField name = new TextField();
name.setCaption( "Type your name here:" );
Button button = new Button( "Click Me" );
button.addClickListener( ( Button.ClickEvent e ) -> {
Label label = new Label( "Thanks " + name.getValue() + ", it works!" + " " + ZonedDateTime.now( ZoneId.systemDefault() ) ); // Moved instantiation of `Label` to its own line so that we can get a reference to pass to the executor service.
layout.addComponent( label ); // Notes current date-time when this object was created.
//
ServletContext servletContext = VaadinServlet.getCurrent().getServletContext();
// The context attribute is stored as `Object`. Cast to `ExecutorService`.
ExecutorService executorService = ( ExecutorService ) servletContext.getAttribute( MyServletContextListener.executorServiceNameForUpdatingLabelAfterSqlService );
if ( null == executorService ) {
System.out.println( "ERROR - Failed to find executor serivce." );
} else {
executorService.submit( new Runnable() {
@Override
public void run () {
// Pretending to access our SQL service. To fake it, let's sleep this thread for a random number of seconds.
int seconds = ThreadLocalRandom.current().nextInt( 4 , 30 + 1 ); // Pass ( min , max + 1 )
try {
Thread.sleep( TimeUnit.SECONDS.toMillis( seconds ) );
} catch ( InterruptedException e ) {
e.printStackTrace();
}
// Upon waking, ask that our `Label` be updated.
ZonedDateTime zdt = ZonedDateTime.now( ZoneId.systemDefault() );
System.out.println( "Updating label at " + zdt );
access( new Runnable() { // Calling `UI::access( Runnable )`, asking that this Runnable be run on the main UI thread rather than on this background thread.
@Override
public void run () {
label.setValue( label.getValue() + " Updated: " + zdt );
}
} );
}
} );
}
} );
layout.addComponents( name , button );
setContent( layout );
}
@WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
@VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
public static class MyUIServlet extends VaadinServlet {
}
}
对象何时会收到更新UI
对象文本的请求。请注意,在此屏幕截图中,运行此应用时,不同的Label
个对象在不同时间以任意顺序更新。
相关问题: