Documentation

Documentation versions (currently viewingVaadin 24)

Vaadin Spring Scopes

Understanding contexts and scopes in Vaadin and Spring.

Contexts and Scopes in Spring

Contexts in Spring are services that manage the lifecycle of objects and handle their injection. A context refers to a situation in which an instance is used with a unique identity. These objects are essentially "singletons" in the context.

Scopes are narrower than contexts. While conventional singletons are application-wide, objects managed by a Spring container are "singletons" in a narrower scope. Examples include a user session, a particular UI instance associated with the session, or even a single request. A scope defines the lifecycle of the object, that is, its creation, use, and destruction.

Read more about beans and scopes in Spring.

Using Scopes with Vaadin & Spring

In most programming languages, a variable name refers to a unique object within the scope of the variable. In Spring, an object has a unique identity within a scope but, instead of identifying the object by its variable name, it’s identified by its type (object class) and qualifiers, if any.

With Spring Boot, you need to mark your Vaadin-related bean classes with the @Component annotation to allow them to be picked up as managed beans. Because there is also a Component class in Vaadin, you can use the @SpringComponent annotation from the Vaadin Spring add-on to avoid having to use fully qualified names. If no scope is specified in addition to the @SpringComponent / @Component annotation, the component is in singleton scope.

In addition to standard Spring scopes, the Vaadin Spring add-on introduces three additional scopes:

  • VaadinSessionScope to target beans to a user’s session

  • UIScope to target beans to a browser window or tab opened by the user

  • RouteScope to target beans to certain routing components pointed to by the RouteScopeOwner qualifier

Overview of Vaadin specific scopes for bean classes (including navigation targets) and their binding to app, session, window/tab and path
Overview of how Vaadin-specific scopes relate to each other when used in navigation targets.
Important
Routing Components are Spring-Initialized Beans
All Flow routing components (@Route, RouterLayout or HasErrorParameter) are initialized by Spring, even without explicitly adding the @SpringComponent annotation. Without any annotations, the components act like a bean in prototype scope. You can inject other beans to the components, but while @PostConstruct methods work, @PreDestroy methods don’t. When you want to use another scope for the components, you need to remember to add the @SpringComponent annotation to get the scope applied.
Note
Changes in Scopes Need Server Restart
When changing the scope annotations, the application server needs to be restarted to apply the new scopes. Restart is required even when live reload is enabled.

VaadinSessionScope

The @VaadinSessionScope annotation maps the Spring beans to the Vaadin session lifecycle. It ensures that the same bean instance is used during the whole Vaadin session.

This example is using the @VaadinSessionScope annotation:

@Route("")
public class MainLayout extends Div {
    public MainLayout(@Autowired SessionService bean) {
        setText(bean.getText());
    }
}

@Route("editor")
public class Editor extends Div {
    public Editor(@Autowired SessionService bean) {
        setText(bean.getText());
    }
}

@SpringComponent
@VaadinSessionScope
public class SessionService {
    private String uid = UUID.randomUUID().toString();

    public String getText(){
        return "session " + uid;
    }
}

Provided you access the application from the same Vaadin session, the same instance of SessionService is used. This is because it’s session-scoped.

If you open the root target in one browser tab, and the editor target in another, the text in both is the same. This happens because the session is the same, even though the tabs (and UI instances) are different.

See Application Lifecycle > User Session for more information on session handling.

UIScope

The @UIScope annotation manages the Spring beans during the UI lifecycle.

This example is using the @UIScope annotation:

@Route("")
public class MainLayout extends Div {
    public MainLayout(@Autowired UIService bean) {
        add(new Span(bean.getText()));
        add(new RouterLink("Open editor", Editor.class));
    }
}

@Route("editor")
public class Editor extends Div {
    public Editor(@Autowired UIService bean) {
        add(new Span(bean.getText()));
        add(new RouterLink("Open MainLayout", MainLayout.class));
    }
}

@SpringComponent
@UIScope
public class UIService {
    private String uid = UUID.randomUUID().toString();

    public String getText() {
        return "ui " + uid;
    }
}

The UIService bean instance is the same inside the same UI. If you open the MainLayout view in one browser tab or window, and the Editor view in another, the text in each is different, because the UI instances are different.

When navigating inside the same browser tab between the MainLayout and Editor, the text stays the same, since the service is the same.

See Application Lifecycle > Loading a UI for more information on UIs.

Note
Preserving UIScope Beans
Unlike with earlier Vaadin versions 7 and 8, the UI and thus the UIScope beans aren’t preserved when the @PreserveOnRefresh annotation is used and the browser is refreshed. To preserve the beans on refresh, you need to use @RouteScope instead (available since V21), as described in the next chapter.

RouteScope & RouteScopeOwner

The @RouteScope annotation ties the beans to the lifecycle of Vaadin Flow routing components (@Route, RouterLayout, HasErrorParameter). Since there can be multiple nested levels of routing components present at once, an additional @RouteScopeOwner qualifier annotation can be used to specify the owner routing component.

Without the owner qualifier, the owner is the currently active routing component at the time of injection. As long as the owner routing component is part of the active view chain, all beans owned by it remain in the scope.

Any routing component can be a @RouteScope bean itself, and the owner can be any parent RouterLayout in the route chain hierarchy.

See Defining Routes With @Route and Router Layouts and Nested Router Targets for more about route targets, route layouts, and the active route chain.

The example here is sharing a bean between two child views with the same parent layout:

@SpringComponent
@RouteScope
@RouteScopeOwner(ParentView.class)
public class RouteService {
    private String uid = UUID.randomUUID().toString();

    public String getText() {
        return "ui " + uid;
    }
}

@Route("")
@RoutePrefix("parent")
public class ParentView extends VerticalLayout
        implements RouterLayout {

    public ParentView(
            @Autowired @RouteScopeOwner(ParentView.class)
            RouteService routeService) {
        add(new Span("Parent view:" + routeService.getText()),
                new RouterLink("Open Child-A", ChildAView.class),
                new RouterLink("Open Child-B", ChildBView.class),
                new RouterLink("Open Sibling", SiblingView.class));
    }
}

@Route(value = "child-a", layout = ParentView.class)
public class ChildAView extends VerticalLayout {

    public ChildAView(
            @Autowired @RouteScopeOwner(ParentView.class)
            RouteService routeService) {
        add(new Text("Child-a: " + routeService.getText()));
    }
}

@Route(value = "child-b", layout = ParentView.class)
public class ChildBView extends VerticalLayout {

    public ChildBView(
            @Autowired @RouteScopeOwner(ParentView.class)
            RouteService routeService) {
        add(new Text("Child-a: " + routeService.getText()));
    }
}

@Route(value = "sibling")
public class SiblingView extends VerticalLayout {

    public SiblingView() {
        add(new RouterLink("Open ParentView", ParentView.class),
                new RouterLink("Open Child-A", ChildAView.class),
                new RouterLink("Open Child-B", ChildBView.class));
    }
}

The injected RouteService bean instance is the same while the ParentView is attached, such as when navigating between the child views.

When navigating to the SiblingView, the ParentView is detached. When navigating back to the ParentView (or child views), a new RouteService bean is created.

Caution
Injecting to Wider Scope
Injecting a "narrower" RouteScope bean into "wider" scope, like parent layout’s RouteScope or UIScope, can cause problems. For example, if you store a RouteScope bean into a UIScope bean, the bean might become stale after navigation.

The @RouteScopeOwner qualifier has to be placed both on top of the bean class and on the injection point of the bean. The annotation can be omitted in the injection point when the bean implementation can be resolved unambiguously by Spring (as it could be in the previous example). However, it’s recommended to have it there for better code readability.

Having an owner view class as a value in the @RouteScopeOwner for a model/business logic bean class ties the application’s view layer to a model/business layer. It can be decoupled, for example, by splitting the bean class into an interface and its implementation class, and then using the interface in the view class and marking the concrete bean implementation class with @RouteScopeOwner.

@RouteScope without @RouteScopeOwner to Replace @ViewScope from Vaadin 7 / 8

When the @RouteScopeOwner annotation is omitted, the owner is the currently active route target. In nested routing hierarchies, the owner is the "leaf" / "bottom-most" routing component, that is, navigation target. The bean remains in scope for as long as the navigation target stays active (attached to the UI).

Compared to a @Scope("prototype") bean injected to the routing component, the @RouteScope bean without an owner has its @PreDestroy method called when the routing component is no longer active. Using @RouteScope without specifying an owner is a replacement for the @ViewScope from Vaadin 7 or 8.

Note
Model-View-Presenter
The following example is based on the model-view-presenter design pattern, for the sake of demonstration. It isn’t a best-practice example. It allows splitting different logical parts of the application, but adds a lot of boilerplate code.

This example shows @RouteScope without owner behaves like the legacy Vaadin @ViewScope:

/*
 * Presenter responsible for application logic and setting data for the view.
 */
@SpringComponent
@RouteScope
public class UserProfilePresenter {

    private final UserService service;
    private final UserModel model;

    @Autowired
    public UserProfilePresenter(UserService service, UserModel model) {
        this.service = service;
    }

    public void init(UserProfileView view) {
        Integer id = model.getActiveUserId();
        if (id != null) {
           view.showUser(service.getUser(id));
        } else {
            view.redirectToLogin();
        }
    }
}

@Route("user-profile")
public class UserProfileView extends VerticalLayout {

    private final UserProfilePresenter presenter;

    public UserProfileView(@Autowired UserProfilePresenter presenter) {
        this.presenter = presenter;
    }

    @PostConstruct
    private void init() {
        presenter.init(this);
    }

    public void showUser(User user) {
        removeAll();
        add(new Div(new Text("Hello " + user.getName())));
    }

    public void redirectToLogin() {
        Notification.show("Not logged in!");
        UI.getCurrent().navigate("login");
    }
}

@SpringComponent
@VaadinSessionScope
// A bean storing the active user for the session
public class UserModel {

    private Integer activeUserId;
    // getter and setter omitted
}

@Service
// Service for fetching the user entity from backend
public class UserService {

    public User getUser(Integer id) {
        // implementation omitted
    }
}
// User entity
public class User {
    private String name;
    // getter and setter omitted
}

In this example, a new UserProfilePresenter bean is created every time the UserProfileView view is opened. The presenter bean stays the same during the time the view is attached to the UI.

Preserving Beans during Browser Refresh

By default, when the user refreshes the page, all routing components are recreated. This applies to @UIScope and @RouteScope beans too; new bean instances are created and injected to the new routing components. It’s possible to tell the framework to preserve the routing components during refresh with the @PreserveOnRefresh annotation (see here for more information).

When the @PreserveOnRefresh annotation is used on a routing component that has @RouteScope beans injected to it, the beans are preserved too.

This example is preserving beans with @RouteScopeOwner targeting a component with @PreserveOnRefresh:

@SpringComponent
@RouteScope
@RouteScopeOwner(MainLayout.class)
public class PreservedBean {
    private String uid = UUID.randomUUID().toString();

    public String getText() {
        return uid;
    }
}

@Route("") // optional, could use a subview with @Route instead
@PreserveOnRefresh
public class MainLayout extends VerticalLayout
        implements RouterLayout {

    public MainLayout(
            @Autowired @RouteScopeOwner(ParentView.class)
            PreservedBean bean) {
        add(new Span("UID:" + bean.getText()));
    }
}

In this example, both the MainLayout component and the PreservedBean injected bean are preserved after browser refresh. The text stays the same.

If the @PreserveOnRefresh annotation is removed from the layout, both the component and the bean are recreated after browser refresh. The text would change.

Beans in UIScope Aren’t Preserved

Injected beans aren’t preserved when they are in UIScope, but only in RouteScope, regardless of whether @PreserveOnRefresh is used. However, any currently active routing components are preserved, even if they are in UIScope. This is due to the nature of the @PreserveOnRefresh feature implementation.

The UI instance itself isn’t preserved, but routing components are. Any bean tied to the UI instance with UIScope is recreated, and the preserved routing components are moved to the new UI. To preserve beans during a browser refresh, you need to use @RouteScope, as shown earlier.

23B703EE-D5C7-44CE-971A-A64EE4D89B7D