Если не обновление пользовательского интерфейса в Vaadin
Ваш вопрос помечен как Vaadin, но, похоже, он задает вопрос только о выполнении фоновой задачи без учета пользовательского интерфейса Vaadin.Если это так, вы задаете общий вопрос Джакартский сервлет , а не вопрос, специфичный для Ваадина.Vaadin - это просто Servlet , хотя и очень большой, изощренный Servlet.
Как вы заметили, написание класса, который реализует ServletContextListener
, является местом для запускакод при запуске веб-приложения перед обслуживанием первого пользователя в методе contextInitialized
.И это место для запуска кода при выходе из вашего веб-приложения после обслуживания последнего пользователя в методе contextDestroyed
.
После написания вашего слушателя вы должны сообщить контейнеру сервлетов (например, Apache Tomcat или Eclipse Jetty) о его существовании.Самый простой способ сделать это - добавить аннотацию @WebListener
.
package com.example.acme;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
*
* @author Basil Bourque
*/
@WebListener
public class AcmeServletContextListener implements ServletContextListener {
@Override
public void contextInitialized ( ServletContextEvent sce ) {
System.out.println ( "Acme Vaadin web app is starting. " );
}
@Override
public void contextDestroyed ( ServletContextEvent sce ) {
System.out.println ( "Acme Vaadin web app is exiting." );
}
}
Не запускать фоновую задачу, используя класс Thread
.Это старая школа.Современный подход использует платформу Executor , добавленную позже в Java.См. Oracle Tutorial .
ExecutorService
Чтобы запустить только одну фоновую задачу, нам нужен пул потоков только из одного потока.Используйте класс Executors
для создания пула потоков.
ExecutorService executorService = Executors.newSingleThreadExecutor() ;
Определите вашу задачу как Runnable
или Callable
.
Runnable runnable = new Runnable ()
{
@Override
public void run ( )
{
System.out.println ( "INFO - Acme web app doing some work on background thread. " + Instant.now () );
}
};
Вы можете использовать более компактный лямбда-синтаксис для определения вашего Runnable
, если хотите.Я использовал длинный синтаксис здесь для ясности.
Сообщите службе-исполнителю, чтобы она запускалась.
executorService.submit ( runnable );
A Future
объект возвращается как дескриптор для проверки хода выполнения или завершения задачи.Возможно, вы не захотите его использовать.
Future future = executorService.submit ( runnable );
Соберите все это вместе, а также код для корректного завершения работы нашего пула потоков (службы исполнителя).И мы добавляем некоторые временные метки в наши сообщения консоли с Instant.now()
.
public class AcmeServletContextListener implements ServletContextListener {
private ExecutorService executorService ;
@Override
public void contextInitialized ( ServletContextEvent sce ) {
System.out.println ( "Acme Vaadin web app is starting. " + Instant.now () );
this.executorService = Executors.newSingleThreadExecutor() ;
Runnable runnable = new Runnable ()
{
@Override
public void run ( )
{
System.out.println ( "INFO - Acme web app doing some work on background thread. " + Instant.now () );
}
};
Future future = this.executorService.submit ( runnable );
}
@Override
public void contextDestroyed ( ServletContextEvent sce ) {
System.out.println ( "Acme Vaadin web app is exiting. " + Instant.now () );
if ( Objects.nonNull ( executorService ) )
{
this.executorService.shutdown ();
}
}
}
ScheduledExecutorService
Если вы хотите запускать эту задачу несколько раз, например, каждые пять минут, не управляйте этим повторением в вашем Runnable
илив течение Thread
.Что касается разделения интересов , мы понимаем, что ваша фоновая задача должна быть сосредоточена исключительно на ее основной задаче, такой как обновление базы данных.Планирование того, когда это должно произойти, и как часто это должно происходить, - это другая работа, которую нужно выполнять в другом месте.
Где обрабатывать планирование?В службе исполнителя, специально созданной для этой работы.Реализация ScheduledExecutorService
имеет методы для запуска задачи один раз, с задержкой или без нее (период ожидания).Или вы можете вызвать методы, чтобы запланировать повторение задачи, скажем, каждые пять минут.
Код, аналогичный приведенному выше.Мы меняем наш ExecutorService
на ScheduledExecutorService
.И мы меняем Executors.newSingleThreadExecutor()
на Executors.newSingleThreadScheduledExecutor()
.Мы указываем начальную задержку и период для повторения, используя перечисление TimeUnit
.Здесь мы используем TimeUnit.MINUTES
с начальной задержкой 2 (подождите две минуты перед первым запуском) и периодом каждые пять минут.Если вы хотите использовать Future
, теперь это тип ScheduledFuture
.
public class AcmeServletContextListener implements ServletContextListener {
private ScheduledExecutorService executorService ;
@Override
public void contextInitialized ( ServletContextEvent sce ) {
System.out.println ( "Acme Vaadin web app is starting. " + Instant.now () );
// Instantiate a thread pool and scheduler.
this.executorService = Executors.newSingleThreadScheduledExecutor() ;
// Define the task to be done.
Runnable runnable = new Runnable ()
{
@Override
public void run ( )
{
System.out.println ( "INFO - Acme web app doing some work on background thread. " + Instant.now () );
}
};
// Tell the scheduler to run the task repeatedly at regular intervals, after an initial delay.
ScheduledFuture future = this.executorService.scheduleAtFixedRate ( runnable , 2 , 5 , TimeUnit.MINUTES );
}
@Override
public void contextDestroyed ( ServletContextEvent sce ) {
System.out.println ( "Acme Vaadin web app is exiting. " + Instant.now () );
if ( Objects.nonNull ( executorService ) )
{
this.executorService.shutdown ();
}
}
}
Важный совет: Оберните свою работу в Runnable в try-catch , чтобы поймать любой Exception
, который может пузыриться.Если исключение (или Error
) достигает запланированной службы исполнителя, эта служба остановит все дальнейшие выполнения.Ваша фоновая задача перестает работать, тихо, таинственно.Лучше перехватывать все непредвиденные исключения (и, возможно, ошибки, которые являются спорными) и сообщать своему системному администратору, если для вас важно продолжать выполнение фоновой задачи.
Jakarta Concurrency
Если выРазвертывание на сервере приложений с поддержкой утилит Jakarta Concurrency (первоначально JSR 236 ) облегчает эту работу .Вам не нужно писать, что ServletContextListener
.Вы можете использовать аннотации, чтобы сервер приложений автоматически запускал ваш Runnable
.
При обновлении пользовательского интерфейса в Vaadin
Возможно, вам нужен отдельный фоновый работник, который каждые пять минут обновляет отображение некоторых ваших пользователей.Если это так, вам нужно несколько частей:
- Фоновый поток, выполняющий некоторую работу за период,
- Реестр обновлений представлений пользователей,
- Много экземпляровпредставления (макет Vaadin или виджет) заинтересованы в обновлении.
Другими словами, ограниченная форма Pub-Sub, шаблон издателя и подписчика , только с одним издателем.
A ServletContextListener
реализацияодин из способов выполнения работы, когда ваше веб-приложение Vaadin запускается (до обслуживания каких-либо пользователей) и когда веб-приложение завершает работу (после обслуживания последнего пользователя).Это хорошее место для запуска и завершения работы вашего издателя pub-sub и реестра.
Вы можете хранить глобальную ссылку на ваш объект реестра в контексте сервлета, отправленного вашей реализации ServletContextListener
.Используйте функцию «атрибуты», коллекцию значений ключей, доступ к которой осуществляется с помощью методов setAttribute
/ getAttribute
/ removeAttribute
.
Если ваш фоновый работник работает в прерывистые периоды, а не непрерывно, узнайте об исполнителяхрамки, в частности, ScheduledExecutorService
.Обязательно корректно отключите любую такую службу исполнителя, поскольку она может пережить ваше веб-приложение и даже ваш контейнер Serlet!Если вы используете полнофункциональный сервер Jakarta EE (такой как Glassfish / Payara, WildFly и т. Д.), А не просто контейнер сервлетов (такой как Apache Tomcat или Eclipse Jetty), вы можете использовать Функция утилит параллелизма для упрощения запуска управляемого запланированного исполнителя с автоматическим запуском / остановом.
Когда вы создаете экземпляр для обновления в пользовательском интерфейсе Vaadin, попросите этот макет или виджет зарегистрироваться в реестре как заинтересованный в получении обновлений.Получите реестр из ServletContext
веб-приложения, как описано в Различные способы получения контекста сервлета .
Я предлагаю сохранить ваш реестр слабые ссылки на заинтересованные взгляды.Эти представления в конечном итоге исчезнут, когда пользователь закроет окно / вкладку веб-браузера.Вы могли бы запрограммировать свой зарегистрированный виджет на изящную отмену регистрации в своем реестре как часть его жизненного цикла.Но я подозреваю, что использование слабых ссылок поможет сделать это надежным.Одна из возможностей - использование WeakHashMap
только с ключами и без значений, где каждый ключ является слабой ссылкой на экземпляр вашего виджета / макета, зарегистрированный для обновлений из фонового потока.
Чтобы фоновый поток обновлял пользовательский интерфейс веб-приложения Vaadin, никогда не обращайтесь к виджетам Vaadin из фонового потока.Поначалу может показаться, что это сработает, но в конечном итоге у вас возникнет конфликт параллелизма , и это может привести к очень плохим последствиям.Вместо этого узнайте, как Vaadin разрешает отправку запроса на обновление с помощью метода access
, передав Runnable
.И вы захотите узнать о технологии Push и о том, как Vaadin делает Push очень простым.Обратите внимание на раздел на этой странице, Вещание другим пользователям , который описывает почти то же самое, что и этот ответ.Попутно вы, вероятно, узнаете о преимуществах и ограничениях WebSockets , который может автоматически использоваться библиотекой Atmosphere , используемой Vaadin для реализации Push.
На протяжении всего этого вы должны очень хорошо понимать проблемы и практики параллелизма и, возможно, ключевое слово volatile
.Контейнер сервлетов Java по определению является многопоточной средой, и теперь вы будете заниматься собственной хореографией этих потоков.Таким образом, вам нужно будет прочитать, перечитать и усердно изучить отличную книгу Параллелизм Java на практике Брайана Гетца и других.
После написания всего этого я понимаю, что ваш Вопрос действительно слишком широк для переполнения стека.Но, надеюсь, этот ответ поможет вам ориентироваться.Вы можете узнать больше о каждой части головоломки, выполнив поиск переполнения стека.В частности, если вы будете искать в переполнении стека, вы найдете несколько очень длинных постов на эти темы, с большим количеством примеров кода, в Vaadin 8. И также обратитесь к Vaadin Forums .Если это жизненно важный проект с финансированием, рассмотрите возможность найма обучения и консультационных услуг , доступных в компании Vaadin Ltd.Ваш проект выполним ;Я сам сделал такой проект по тем же принципам, что и здесь.Это не просто, но возможно, и это довольно интересная работа.