복붙노트

[SPRING] Vaadin : 데이터가 반환 된 후 UI 업데이트

SPRING

Vaadin : 데이터가 반환 된 후 UI 업데이트

@SpringUI
public class VaadinUI extends UI {
  ...
  String sql = "SELECT * FROM table1";
  button.addClickListener(e -> layout.addComponent(new Label(service.evalSql(sql))));
  ...

현재 버튼이 눌려지면, 페이지는 evalSql ()이 새로운 Label을 추가하기 전에 데이터베이스로부터 결과를 얻기를 기다립니다.

단추를 누를 때 새 레이블이 즉시 추가되고 초기 자리 표시 자 문자열 ( "가져 오는 결과 ..")로 설정되지만 데이터베이스에서 결과가 반환 된 후 결과 문자열로 업데이트 될 때이를 어떻게 변경할 수 있습니까?

해결법

  1. ==============================

    1.UI에서 @Push를 사용해야하고 UI.access (..)를 사용하여 쿼리가 완료되면 Label의 내용을 업데이트해야합니다. 몇 가지 예제가있는 좋은 문서가 있습니다.

    UI에서 @Push를 사용해야하고 UI.access (..)를 사용하여 쿼리가 완료되면 Label의 내용을 업데이트해야합니다. 몇 가지 예제가있는 좋은 문서가 있습니다.

    https://vaadin.com/docs/v8/framework/advanced/advanced-push.html

  2. ==============================

    2.좋은 소식은 사용자가 UI의 응답 성을 차단하지 않고 서버에서 백그라운드로 수행 한 작업으로 Vaadin 사용자 인터페이스의 위젯을 나중에 업데이트하도록 사용자가 원하는대로 할 수 있다는 것입니다. 그것은 Vaadin과 Java 기반 백엔드로 아주 잘 수행 될 수 있습니다.

    좋은 소식은 사용자가 UI의 응답 성을 차단하지 않고 서버에서 백그라운드로 수행 한 작업으로 Vaadin 사용자 인터페이스의 위젯을 나중에 업데이트하도록 사용자가 원하는대로 할 수 있다는 것입니다. 그것은 Vaadin과 Java 기반 백엔드로 아주 잘 수행 될 수 있습니다.

    나쁜 소식은 동시성과 쓰레딩에 익숙하지 않다면 오르는 학습 곡선이 있다는 것입니다.

    백그라운드에서 무언가를 수행하고 차단하지 않고 나중에 다시 확인하도록하는 기술적 용어는 비동기 업데이트입니다.

    스레드를 사용하여 Java에서이를 수행 할 수 있습니다. SQL 서비스 코드를 실행하는 스레드를 생성하십시오. 해당 코드가 데이터베이스 작업을 완료하면 해당 코드는 UI :: access (Runnable runnable)를 호출하여 요청을 게시하여 원래의 UI (사용자 인터페이스) 스레드에서 Label 위젯을 업데이트하도록합니다.

    Answer by Lund 레이블 위젯 업데이트에서 설명했듯이 Push Technology는 서버 측 생성 이벤트에서 브라우저를 업데이트해야합니다. 다행스럽게도 Vaadin 8 이상은 Push에 대한 탁월한 지원을 제공하고 App에 Push를 매우 쉽게 만들 수 있습니다.

    팁 : 일반적으로 Push, 특히 WebSocket은 최근 몇 년 동안 크게 발전했습니다. 최신 세대의 서블릿 컨테이너를 사용하면 경험이 향상됩니다. 예를 들어 Tomcat을 사용하는 경우 최신 버전의 Tomcat 8.5 또는 9를 사용하는 것이 좋습니다.

    Java는 스레딩에 대한 탁월한 지원을 제공합니다. 필요한 작업 대부분은 Java로 작성된 Executor 프레임 워크에 의해 처리됩니다.

    당신이 스레딩을 처음 접한다면, 당신은 진지한 학습을해야합니다. 먼저 동시성에 대한 Oracle Tutorial을 공부하십시오. 결국 Brian Goetz 외 다수가 Java Concurrency in Practice라는 훌륭한 책을 몇 차례 읽고 다시 읽어야 할 것입니다.

    Vaadin 앱이 실행되고 종료 될 때 스레드 저글링 실행 프로그램 서비스를 설정하고 해체하려고 할 것입니다. 이를 수행하는 방법은 Vaadin 서블릿 클래스와 별도로 클래스를 작성하는 것입니다. 이 클래스는 ServletContextListener를 구현해야합니다. 두 가지 필수 메소드를 구현하고 @WebListener로 클래스에 주석을 추가하면 쉽게 구현할 수 있습니다.

    실행 프로그램 서비스를 분리하십시오. 그렇지 않으면 관리하는 백그라운드 스레드가 Vaadin 앱의 종료 및 웹 컨테이너 (Tomcat, Jetty 등)의 종료까지 계속 무기한으로 계속 실행됩니다.

    이 작업의 핵심 아이디어 : 백그라운드에서 직접 Vaadin UI 위젯에 액세스하지 마십시오. 백그라운드 위젯에서 실행중인 코드의 위젯에서 UI 위젯에 대한 메소드를 호출하거나 값에 액세스하지 마십시오. UI 위젯은 스레드로부터 안전하지 않기 때문에 (사용자 인터페이스 기술을 스레드로부터 안전하게 만드는 것은 대단히 어렵습니다). 당신은 그런 배경 전화로 도망 갈 수도 있고, 또는 런타임에 끔찍한 일이 일어날 수도 있습니다.

    웹 컨테이너 (예 : Tomcat 또는 Jetty) 또는 웹 프로필 서버 (예 : TomEE)가 아닌 본격적인 Jakarta EE (이전에는 Java EE로 알려짐) 서버를 사용하는 경우, Executor 서비스와 ServletContextListener가 완료됩니다. Java EE 7 이상에서 정의 된 기능 사용 : JSR 236 : JavaTM EE 용 동시성 유틸리티

    귀하의 질문에 봄 태그가 붙어 있습니다. Spring은이 작업을 도와주는 기능을 가지고있을 것이다. 나는 스프링 사용자가 아니기 때문에 나는 모른다. 아마도 스프링 TaskExecutor?

    Stack Overflow를 검색하면이 모든 주제가 해결되었음을 알 수 있습니다.

    Vaadin with Push를 보여주는 두 가지 예제 앱을 이미 게시했습니다.

    Vaadin Ltd.에서 제공하는 Maven archetype 인 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 프레임 워크는 이러한 스레드 작업에 많은 어려움을 겪습니다. 모든 사용자의 웹 브라우저 창에 추가되는 모든 새 레이블을 모두 업데이트하기 위해 스레드 풀에 의해 지원되는 실행자 서비스를 구축해야합니다. 문제는 Executor 서비스 객체를 설정, 저장 및 해체하는 것입니다.

    우리는 웹 응용 프로그램의 전체 수명주기 동안 실행 프로그램 서비스를 사용할 수 있기를 바랍니다. 첫 번째 사용자 요청이 새롭게 시작된 웹 응용 프로그램에 도착하기 전에 실행 프로그램 서비스를 설정하려고합니다. 우리가 웹 애플리케이션을 종료하려고 할 때, 실행자 서비스를 해체해야 백업 스레 풀의 스레드가 종료됩니다. Vaadin 웹 앱의 라이프 사이클에 어떻게 연결 되나요?

    음, Vaadin은 매우 크고 정교한 서블릿이지만 Java Servlet을 구현 한 것입니다. 서블릿 용어에서 웹 애플리케이션은 "컨텍스트"로 알려져 있습니다. 서블릿 스펙에서는 Tomcat, Jetty 등과 같은 모든 서블릿 컨테이너가 특정 이벤트에 대한 리스너로 표시된 클래스를 통지해야합니다. 이것을 이용하려면 ServletContextListener 인터페이스를 구현하는 클래스 인 Vaadin 앱에 또 다른 클래스를 추가해야합니다.

    새 클래스에 @WebListener로 주석을 추가하면 Servlet 컨테이너가이 클래스를 인식하고 웹 응용 프로그램을 시작하면 리스너 객체가 인스턴스화 된 다음 적절한 시간에 메소드를 호출합니다. contextInitialized 메소드는 서블릿이 제대로 초기화되었지만 들어오는 웹 브라우저 요청이 처리되기 전에 호출됩니다. contextDestroyed 메서드는 마지막 응답이 사용자에게 다시 전송 된 후 마지막 웹 브라우저 요청이 처리 된 후에 호출됩니다.

    그래서 ServletContextListener를 구현하는 우리 클래스는 executor 서비스를 백업 스레드 풀로 설정하고 해체하기에 완벽한 장소입니다.

    한 가지 더 문제 : Executor 서비스를 설정 한 후에, 사용자가 Label 객체를 추가 할 때 Vaadin 서블릿에서 참조를 찾아서 나중에 어디에 사용합니까? 한 가지 해결책은 Executor 서비스 참조를 "컨텍스트"(우리의 웹 응용 프로그램)에 "속성"으로 저장하는 것입니다. 서블릿 스펙에서는 모든 서블릿 컨테이너가 각 컨텍스트 (웹 앱)에 키가 String 객체이고 값이 Object 객체 인 간단한 키 - 값 콜렉션을 제공해야합니다. 우리는 실행자 서비스를 식별하기 위해 문자열을 고안 할 수 있으며, 이후 Vaadin 서블릿은 나중에 실행기 서비스를 검색하기 위해 루프 업을 수행 할 수 있습니다.

    아이러니하게도, 위의 논의는 실제 코드보다 길다!

    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 앱으로 돌아갑니다. 해당 파일 수정 :

    스레딩과 동시성에 익숙하지 않다면, 이것은 매우 어려울 수 있습니다. 이 코드를 공부하고 잠잠해질 때가 있습니다.이 방법을 다른 방식으로 구성 할 수는 있지만 여기서는 교육 목적으로 간단하게 만들고 싶습니다. 코드의 구조 / 배열이 아니라 다음과 같은 아이디어에 중점을 둡니다.

    여기 수정 된 Vaadin 앱이 있습니다.

    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 객체의 텍스트를 업데이트하라는 요청을받을 때 당신은 알 수 없습니다. 이 스크린 샷에서이 앱을 실행하는 동안 다른 Label 객체가 임의의 순서로 다른 시간에 업데이트되었음을 ​​알 수 있습니다.

    관련 질문 :

  3. from https://stackoverflow.com/questions/50880506/vaadin-update-ui-after-data-returned by cc-by-sa and MIT license