diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
index 93e1df23855..1424db3eab5 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java
@@ -46,6 +46,7 @@
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.slf4j.Logger;
@@ -589,14 +590,21 @@ public Registration addUIInitListener(UIInitListener listener) {
/**
* Adds a listener that gets notified when a Vaadin service session that has
* been initialized for this service is destroyed.
+ *
*
* The session being destroyed is locked and its UIs have been removed when
* the listeners are called.
*
+ *
+ * This method delivers notifications for all associated sessions. To be
+ * notified for only one specific session, use
+ * {@link VaadinSession#addSessionDestroyListener}.
+ *
* @param listener
* the vaadin service session destroy listener
* @return a handle that can be used for removing the listener
* @see #addSessionInitListener(SessionInitListener)
+ * @see VaadinSession#addSessionDestroyListener
*/
public Registration addSessionDestroyListener(
SessionDestroyListener listener) {
@@ -652,18 +660,19 @@ public void fireSessionDestroy(VaadinSession vaadinSession) {
}
SessionDestroyEvent event = new SessionDestroyEvent(
VaadinService.this, session);
- for (SessionDestroyListener listener : sessionDestroyListeners) {
- try {
- listener.sessionDestroy(event);
- } catch (Exception e) {
- /*
- * for now, use the session error handler; in the future,
- * could have an API for using some other handler for
- * session init and destroy listeners
- */
- session.getErrorHandler().error(new ErrorEvent(e));
- }
- }
+ Stream.concat(session.destroyListeners.stream(),
+ sessionDestroyListeners.stream()).forEach(listener -> {
+ try {
+ listener.sessionDestroy(event);
+ } catch (Exception e) {
+ /*
+ * for now, use the session error handler; in the
+ * future, could have an API for using some other
+ * handler for session init and destroy listeners
+ */
+ session.getErrorHandler().error(new ErrorEvent(e));
+ }
+ });
session.setState(VaadinSessionState.CLOSED);
});
diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinSession.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinSession.java
index d98586c189d..9d897229072 100644
--- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinSession.java
+++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinSession.java
@@ -25,6 +25,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@@ -32,6 +33,7 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -47,6 +49,7 @@
import com.vaadin.flow.internal.CurrentInstance;
import com.vaadin.flow.internal.StateNode;
import com.vaadin.flow.server.startup.ApplicationConfiguration;
+import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.shared.communication.PushMode;
import jakarta.servlet.http.HttpSession;
@@ -78,6 +81,8 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
volatile boolean sessionClosedExplicitly = false;
+ final List destroyListeners = new CopyOnWriteArrayList<>();
+
/**
* Configuration for the session.
*/
@@ -489,6 +494,31 @@ public Collection getRequestHandlers() {
return Collections.unmodifiableCollection(requestHandlers);
}
+ /**
+ * Adds a listener that gets notified when this session is destroyed.
+ *
+ *
+ * This session will be locked and its {@link UI}s will have been removed
+ * when the listener is called.
+ *
+ *
+ * If this session is already closed, no notification is delivered.
+ *
+ *
+ * This method only delivers notifications for this session. To also be
+ * notified about other sessions, use
+ * {@link VaadinService#addSessionDestroyListener}.
+ *
+ * @param listener
+ * the session destroy listener
+ * @return a handle that can be used for removing the listener
+ * @see VaadinService#addSessionDestroyListener
+ */
+ public Registration addSessionDestroyListener(
+ SessionDestroyListener listener) {
+ return Registration.addAndRemove(destroyListeners, listener);
+ }
+
/**
* Gets the currently used session. The current session is automatically
* defined when processing requests to the server (see {@link ThreadLocal})
diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringVaadinServletService.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringVaadinServletService.java
index b41c3ab0816..075b8d607a4 100644
--- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringVaadinServletService.java
+++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringVaadinServletService.java
@@ -46,8 +46,6 @@ public class SpringVaadinServletService extends VaadinServletService {
private final transient ApplicationContext context;
- private final Registration serviceDestroyRegistration;
-
static final String SPRING_BOOT_WEBPROPERTIES_CLASS = "org.springframework.boot.autoconfigure.web.WebProperties";
/**
@@ -66,11 +64,6 @@ public SpringVaadinServletService(VaadinServlet servlet,
ApplicationContext context) {
super(servlet, deploymentConfiguration);
this.context = context;
- SessionDestroyListener listener = event -> sessionDestroyed(
- event.getSession());
- Registration registration = addSessionDestroyListener(listener);
- serviceDestroyRegistration = addServiceDestroyListener(
- event -> serviceDestroyed(registration));
}
@Override
@@ -105,21 +98,13 @@ public void init() throws ServiceException {
uiInitListeners.values().forEach(this::addUIInitListener);
}
+ // This method should be removed when the deprecated class
+ // SpringVaadinSession is removed
@Override
protected VaadinSession createVaadinSession(VaadinRequest request) {
return new SpringVaadinSession(this);
}
- private void sessionDestroyed(VaadinSession session) {
- assert session instanceof SpringVaadinSession;
- ((SpringVaadinSession) session).fireSessionDestroy();
- }
-
- private void serviceDestroyed(Registration registration) {
- registration.remove();
- serviceDestroyRegistration.remove();
- }
-
@Override
public URL getStaticResource(String path) {
URL resource = super.getStaticResource(path);
diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringVaadinSession.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringVaadinSession.java
index 33eba0e5ca8..d2dcf5ad5a9 100644
--- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringVaadinSession.java
+++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringVaadinSession.java
@@ -15,12 +15,7 @@
*/
package com.vaadin.flow.spring;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import com.vaadin.flow.server.SessionDestroyEvent;
import com.vaadin.flow.server.SessionDestroyListener;
-import com.vaadin.flow.server.SessionInitListener;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
@@ -28,14 +23,13 @@
* Vaadin session implementation for Spring.
*
* @author Vaadin Ltd
- *
+ * @deprecated No replacement planned
*/
+@Deprecated(forRemoval = true)
public class SpringVaadinSession extends VaadinSession {
- private final List destroyListeners = new CopyOnWriteArrayList<>();
-
/**
- * Creates a new VaadinSession tied to a VaadinService.
+ * Creates a new SpringVaadinSession tied to a VaadinService.
*
* @param service
* the Vaadin service for the new session
@@ -48,10 +42,7 @@ public SpringVaadinSession(VaadinService service) {
* Handles destruction of the session.
*/
public void fireSessionDestroy() {
- SessionDestroyEvent event = new SessionDestroyEvent(getService(), this);
- destroyListeners.stream()
- .forEach(listener -> listener.sessionDestroy(event));
- destroyListeners.clear();
+ getService().fireSessionDestroy(this);
}
/**
@@ -70,7 +61,7 @@ public void fireSessionDestroy() {
* the vaadin service session destroy listener
*/
public void addDestroyListener(SessionDestroyListener listener) {
- destroyListeners.add(listener);
+ this.addSessionDestroyListener(listener);
}
}
diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinRouteScope.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinRouteScope.java
index 16e654ef8e0..c179f339df8 100644
--- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinRouteScope.java
+++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinRouteScope.java
@@ -50,7 +50,6 @@
import com.vaadin.flow.server.VaadinServletContext;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.shared.Registration;
-import com.vaadin.flow.spring.SpringVaadinSession;
import com.vaadin.flow.spring.annotation.RouteScopeOwner;
/**
@@ -72,22 +71,13 @@ private static class RouteStoreWrapper implements Serializable {
private final VaadinSession session;
- private final Registration sessionDestroyListenerRegistration;
-
private final Map routeStores;
private RouteStoreWrapper(VaadinSession session) {
assert session.hasLock();
this.session = session;
+ session.addSessionDestroyListener(event -> destroy());
routeStores = new HashMap<>();
- if (session instanceof SpringVaadinSession) {
- sessionDestroyListenerRegistration = null;
- ((SpringVaadinSession) session)
- .addDestroyListener(event -> destroy());
- } else {
- sessionDestroyListenerRegistration = session.getService()
- .addSessionDestroyListener(event -> destroy());
- }
}
private RouteBeanStore getBeanStore(UI ui) {
@@ -146,9 +136,6 @@ private void destroy() {
routeStores.clear();
} finally {
session.unlock();
- if (sessionDestroyListenerRegistration != null) {
- sessionDestroyListenerRegistration.remove();
- }
}
}
diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinSessionScope.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinSessionScope.java
index 73b48ed2d68..a4893aaa204 100644
--- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinSessionScope.java
+++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinSessionScope.java
@@ -20,7 +20,6 @@
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.shared.Registration;
-import com.vaadin.flow.spring.SpringVaadinSession;
/**
* Implementation of Spring's
@@ -40,31 +39,15 @@ public class VaadinSessionScope extends AbstractScope {
private static class SessionBeanStore extends BeanStore {
- private final Registration sessionDestroyListenerRegistration;
-
private SessionBeanStore(VaadinSession session) {
super(session);
- if (session instanceof SpringVaadinSession) {
- sessionDestroyListenerRegistration = null;
- ((SpringVaadinSession) session)
- .addDestroyListener(event -> destroy());
- } else {
- sessionDestroyListenerRegistration = session.getService()
- .addSessionDestroyListener(event -> destroy());
- }
+ session.addSessionDestroyListener(event -> destroy());
}
@Override
Void doDestroy() {
- try {
- getVaadinSession().setAttribute(BeanStore.class, null);
- super.doDestroy();
- } finally {
- if (sessionDestroyListenerRegistration != null) {
- sessionDestroyListenerRegistration.remove();
- }
- }
- return null;
+ getVaadinSession().setAttribute(BeanStore.class, null);
+ return super.doDestroy();
}
}
diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinUIScope.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinUIScope.java
index cbb700cdbeb..bfc11b79203 100644
--- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinUIScope.java
+++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinUIScope.java
@@ -26,7 +26,6 @@
import com.vaadin.flow.component.UI;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.shared.Registration;
-import com.vaadin.flow.spring.SpringVaadinSession;
/**
* Implementation of Spring's
@@ -48,22 +47,13 @@ private static class UIStoreWrapper
private final VaadinSession session;
- private final Registration sessionDestroyListenerRegistration;
-
private final Map uiStores;
private UIStoreWrapper(VaadinSession session) {
assert session.hasLock();
uiStores = new HashMap<>();
this.session = session;
- if (session instanceof SpringVaadinSession) {
- sessionDestroyListenerRegistration = null;
- ((SpringVaadinSession) session)
- .addDestroyListener(event -> destroy());
- } else {
- sessionDestroyListenerRegistration = session.getService()
- .addSessionDestroyListener(event -> destroy());
- }
+ session.addSessionDestroyListener(event -> destroy());
}
@Override
@@ -96,9 +86,6 @@ private void destroy() {
uiStores.clear();
} finally {
session.unlock();
- if (sessionDestroyListenerRegistration != null) {
- sessionDestroyListenerRegistration.remove();
- }
}
}
diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/AbstractScopeTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/AbstractScopeTest.java
index 17b663f8a38..759454a5acd 100644
--- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/AbstractScopeTest.java
+++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/AbstractScopeTest.java
@@ -18,6 +18,7 @@
import java.util.Collections;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import org.junit.After;
@@ -35,7 +36,6 @@
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.server.VaadinSessionState;
import com.vaadin.flow.server.startup.ApplicationConfiguration;
-import com.vaadin.flow.spring.SpringVaadinSession;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;
@@ -46,12 +46,18 @@ public abstract class AbstractScopeTest {
private VaadinSession session;
- public static class TestSession extends SpringVaadinSession {
+ public static class TestSession extends VaadinSession {
+
+ private final ReentrantLock lock = new ReentrantLock();
public TestSession() {
- super(Mockito.mock(VaadinService.class));
+ super(Mockito.spy(VaadinService.class));
}
+ @Override
+ public ReentrantLock getLockInstance() {
+ return this.lock;
+ }
}
@After
@@ -131,7 +137,7 @@ protected void registerDestructionCallback_currentScopeIsSet_objectIsStored(
@SuppressWarnings("unchecked")
protected VaadinSession mockSession() {
- SpringVaadinSession session = Mockito.mock(TestSession.class,
+ VaadinSession session = Mockito.mock(TestSession.class,
Mockito.withSettings().useConstructor());
doCallRealMethod().when(session).setAttribute(Mockito.any(Class.class),
Mockito.any());
diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinRouteScopeTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinRouteScopeTest.java
index c5d84690e30..3ed0babf774 100644
--- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinRouteScopeTest.java
+++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinRouteScopeTest.java
@@ -37,7 +37,6 @@
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinServletContext;
import com.vaadin.flow.server.VaadinSession;
-import com.vaadin.flow.spring.SpringVaadinSession;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;
@@ -89,13 +88,14 @@ public void destroySession_sessionAttributeIsCleanedAndDestructionCallbackIsCall
mockServletContext(ui);
- SpringVaadinSession springSession = (SpringVaadinSession) VaadinSession
- .getCurrent();
+ VaadinSession session = VaadinSession.getCurrent();
+ VaadinService service = session.getService();
- doCallRealMethod().when(springSession)
- .addDestroyListener(Mockito.any());
-
- doCallRealMethod().when(springSession).fireSessionDestroy();
+ doCallRealMethod().when(session)
+ .addSessionDestroyListener(Mockito.any());
+ doCallRealMethod().when(session).getLockInstance();
+ doCallRealMethod().when(session).getPendingAccessQueue();
+ doCallRealMethod().when(session).access(Mockito.any());
VaadinRouteScope scope = initScope(ui);
@@ -108,12 +108,13 @@ public void destroySession_sessionAttributeIsCleanedAndDestructionCallbackIsCall
+ "$RouteStoreWrapper";
// self control - the attribute name is used by the implementation
- Assert.assertNotNull(springSession.getAttribute(attribute));
+ Assert.assertNotNull(session.getAttribute(attribute));
- springSession.fireSessionDestroy();
+ service.fireSessionDestroy(session);
+ service.runPendingAccessTasks(session);
Assert.assertEquals(1, count.get());
- Assert.assertNull(springSession.getAttribute(attribute));
+ Assert.assertNull(session.getAttribute(attribute));
// Destruction callbacks are not called anymore (they are removed)
scope.getBeanStore().destroy();
diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinSessionScopeTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinSessionScopeTest.java
index e857ea7fd45..7e95740b0ad 100644
--- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinSessionScopeTest.java
+++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinSessionScopeTest.java
@@ -29,8 +29,8 @@
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
+import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
-import com.vaadin.flow.spring.SpringVaadinSession;
import net.jcip.annotations.NotThreadSafe;
@@ -71,12 +71,13 @@ public void registerDestructionCallback_currentSessionIsSet_objectIsStored() {
@Test
public void destroySession_sessionAttributeIsCleanedAndDestructionCallbackIsCalled() {
VaadinSession session = mockSession();
- SpringVaadinSession springSession = (SpringVaadinSession) session;
+ VaadinService service = session.getService();
- doCallRealMethod().when(springSession)
- .addDestroyListener(Mockito.any());
-
- doCallRealMethod().when(springSession).fireSessionDestroy();
+ doCallRealMethod().when(session)
+ .addSessionDestroyListener(Mockito.any());
+ doCallRealMethod().when(session).getLockInstance();
+ doCallRealMethod().when(session).getPendingAccessQueue();
+ doCallRealMethod().when(session).access(Mockito.any());
VaadinSessionScope scope = new VaadinSessionScope();
@@ -89,7 +90,8 @@ public void destroySession_sessionAttributeIsCleanedAndDestructionCallbackIsCall
when(factory.getObject()).thenReturn(object);
scope.get("foo", factory);
- springSession.fireSessionDestroy();
+ service.fireSessionDestroy(session);
+ service.runPendingAccessTasks(session);
Assert.assertEquals(1, count.get());
Assert.assertNull(session.getAttribute(BeanStore.class));
diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinUIScopeTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinUIScopeTest.java
index a8c7f7e25b9..54e495823e1 100644
--- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinUIScopeTest.java
+++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinUIScopeTest.java
@@ -25,8 +25,8 @@
import com.vaadin.flow.component.ComponentUtil;
import com.vaadin.flow.component.UI;
+import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
-import com.vaadin.flow.spring.SpringVaadinSession;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;
@@ -89,13 +89,14 @@ public void remove_noCurrentUI_throwException() {
public void destroySession_sessionAttributeIsCleanedAndDestructionCallbackIsCalled() {
mockUI();
- SpringVaadinSession springSession = (SpringVaadinSession) VaadinSession
- .getCurrent();
+ VaadinSession session = VaadinSession.getCurrent();
+ VaadinService service = session.getService();
- doCallRealMethod().when(springSession)
- .addDestroyListener(Mockito.any());
-
- doCallRealMethod().when(springSession).fireSessionDestroy();
+ doCallRealMethod().when(session)
+ .addSessionDestroyListener(Mockito.any());
+ doCallRealMethod().when(session).getLockInstance();
+ doCallRealMethod().when(session).getPendingAccessQueue();
+ doCallRealMethod().when(session).access(Mockito.any());
VaadinUIScope scope = new VaadinUIScope();
@@ -111,12 +112,13 @@ public void destroySession_sessionAttributeIsCleanedAndDestructionCallbackIsCall
String attribute = VaadinUIScope.class.getName() + "$UIStoreWrapper";
// self control - the attribute name is used by the implementation
- Assert.assertNotNull(springSession.getAttribute(attribute));
+ Assert.assertNotNull(session.getAttribute(attribute));
- springSession.fireSessionDestroy();
+ service.fireSessionDestroy(session);
+ service.runPendingAccessTasks(session);
Assert.assertEquals(1, count.get());
- Assert.assertNull(springSession.getAttribute(attribute));
+ Assert.assertNull(session.getAttribute(attribute));
// Destruction callbacks are not called anymore (they are removed)
scope.getBeanStore().destroy();