Chris K. opened SPR-16998 and commented

We use Spring in context with Vaadin 8.2 with the Spring addon.

Each View is a Spring managed bean. We have e.g. the following views:

@SpringView( name = CalendarView.VIEW_NAME )
public class AdminCalendarView
{
  @PostConstruct
  private void postConstruct()
  {
    ...
  }

  @EventListener
  public void toggleLeftZone( UpdateZoneEvent event )
  {
    ...
  }
}

and

@SpringView( name = CalendarView.VIEW_NAME )
public class UserCalendarView
{

  @PostConstruct
  private void postConstruct()
  {
    ...
  }

  @EventListener
  public void toggleLeftZone( UpdateZoneEvent event )
  {
    ...
  }
}

SpringView:

@Target({ java.lang.annotation.ElementType.TYPE })
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Documented
@SpringComponent
@ViewScope
public @interface SpringView 
{
  ...
}

ViewScope:

@Scope(ViewScopeImpl.VAADIN_VIEW_SCOPE_NAME) //VAADIN_VIEW_SCOPE_NAME = "vaadin-view";
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ViewScope {

}

Note: * these are 2 different views based on the logged in user with corresponding permissions * These views are NO singeltons but Vaadin Session Scope

When a user (NOT ADMIN) is logged in and navigates to the UserCalendarView it is instantiated by Spring. If the UpdateZoneEvent raises this View gets notified.

BUT: Also AdminCalendarView gets instantiated and receives this event.

This is not intended since the admin View MUST NOT created in a non admin user session. This is a huge performance and security problem since beans are instantiated which are not used in a particular session and therefore must not created in a user session.

It is possible to receive this event (or any event) only for already created beans?

Thanks a lot for your help.


Affects: 5.0.6

Attachments: - demo.zip (67.97 kB)

Comment From: spring-projects-issues

Chris K. commented

Hi.

Are there any news on this issue?

Regards,

Chris

Comment From: spring-projects-issues

Zhang Jie commented

Will it work if you use subclasses AdminUpdateZoneEvent and UserUpdateZoneEvent instead of UpdateZoneEvent in toggleLeftZone method?

Comment From: spring-projects-issues

Chris K. commented

Hi.

Thanks for your suggestion.

Now the right view gets the event depending on who is logged in. BUT the root cause still exists.

I log in as admin

I never call the AdminCalenderView (The session scoped bean is not instantiated, yet)

When I call the toggleLeftZone method a AdminCalenderView bean gets instantiated and the event is dispatched to that bean.

Comment From: spring-projects-issues

Zhang Jie commented

That toggleLeftZone method will be involved automaticlly when event is broardcasted in other place, what do you mean "I call the toggleLeftZone method"?

Besides, when a UpdateZoneEvent is broardcasted, all EventListener methods that support this event will be involved (include both AdminCalendarViews' and UserCalendarViews'), if you create different event, only the method which support the specific event will be involved.

Comment From: spring-projects-issues

Chris K. commented

The logged in User can click a Button which calls a global toggleLeftZone method.

This one:

/**
 * Toggles the visibility state of the left zone.
 */
public void toggleLeftZone()
{
    this.isLeftZoneVisible = !this.isLeftZoneVisible;

    if ( currentUser.isAdmin() )
    {
        eventSvc.publish( new AdminUpdateZoneEvent( Zone.LEFT ) );
    }
    else
    {
        eventSvc.publish( new SupplierUpdateZoneEvent( Zone.LEFT ) );
    }
}

EventService internally calls publisher.publishEvent( event ); where publisher is an instance of Spring's ApplicationEventPublisher.

I created two different events (one for admin, and one for regular user) as you mentioned above. Now the right views receives the correct event.

@SpringView( name = CalendarView.VIEW_NAME )
public class UserCalendarView 
{
  @PostConstruct
  private void postConstruct()
  {
    ...
  }

  @EventListener
  public void toggleLeftZone( SupplierUpdateZoneEvent event )
  {
    ...
  }
}

and for admin

@SpringView( name = CalendarView.VIEW_NAME )
public class AdminCalendarView
{
  @PostConstruct
  private void postConstruct()
  {
    ...
  }

  @EventListener
  public void toggleLeftZone( AdminUpdateZoneEvent event )
  {
    ...
  }
}

But the issue persists. If there is no instance in the current session of a AdminCalenderView or UserCalenderView and the event is published, these beans are created.

If you like, I can show you a debug session via skype.

Comment From: spring-projects-issues

Zhang Jie commented

When you start application, these beans are allready created because of @SpringView (actually @Component), and thess beans are used for checking whether supporting event. The only thing you can do is to involve correct EventListener when specific event occurs. Besides, the ViewScope is created and used by vaadin, maybe you can ask for help from vaadin?

Comment From: spring-projects-issues

Chris K. commented

These beans are NOT initialiced when the application starts because they have a session scope. The beans are created when the logged in user navigates to a specific view. In this case the calendar view.

The same issue exists with standard prototype beans.

Each logged in user have an own session and therefore an own view instance.

Comment From: spring-projects-issues

Zhang Jie commented

You mentioned "The beans are created when the logged in user navigates to a specific view", yes, but according to ViewScopeImpl, the ViewScoped bean will be cached, it's hard to make sure only one ViewScoped bean exist in context, unless you destroy it when finish using it.

If you don't want both AdminCalendarView and UserCalendarView recieve the same event, you can user different event type. If you want to controll access to AdminCalendarView and UserCalendarView, you can use interface ViewAccessControl.

P.S. According to SpringViewProvider.getView() method, the first accessible view will be returned, maybe you really should consider ViewAccessControl because both AdminCalendarView and UserCalendarView have the same viewname.

Comment From: spring-projects-issues

Chris K. commented

I implemented ViewInstanceAccessControl on my own to be sure that the same view name returns the correct view class based on the logged in user. I try to create a simple spring app which reproduces the issue without vaadin.

Comment From: spring-projects-issues

Chris K. commented

Hi. I attached a simple Spring Boot Application which reproduces the issue with mvc pattern and NO Vaadin at all.

After starting the application visit http://localhost:8080 Please have look at comments, Console and the webpages. Thanks in advance to have a look into it.

Comment From: spring-projects-issues

Zhang Jie commented

I debugged your sample. I found that there may be a misunderstanding.

The mechanism of EventListener can be described as blows: Spring create a ApplicationListenerMethodAdapter to wrap the EventListener method, which contain the bean name(not the bean). When publisher.publishEvent(new UpdateEvent()) happens, ApplicationListenerMethodAdapter's onApplicationEvent will be involved (ignore supportsEventType() in the discussion), and because the condition in EventListener is empty, the method doInvoke() is always involved, the bean which contains EventListener method wil be created by this line in source, and obey the Scope mechanism.So you can see ReceiverBean allso receives the UpdateEvent and print log.The whole mechanism is by design.

There are two ways to make sure ReceiverBean can't receive the UpdateEvent: First, as I mentioned before, using different Event in different EventListener method; Second, use the same Event but set EventListener's condition. For example, I set a property value in UpdateEvent. I set EventListener's condition using SpEL "#root.args[0].value == 'no'" inside ReceiverBean. When I publish UpdateEvent with value "yes", only EventListener method in GreetingController will receive the event, and ReceiverBean is't created. When I publish UpdateEvent with value "no", both EventListener method in GreetingController will receive the event, and both bean are created.

Comment From: spring-projects-issues

Chris K. commented

Thanks for your time and explanation. I understand your solutions

But I have doubts if this would be a nice solution. Because: * The application code needs to decide (at the time of sending the event) which beans should receive the event under certain conditions (produces boilerplate code) * The application might not know (at the time of sending the event) which beans are currently instantiated and alive in the ApplicationContext and which are not, e.g. all "prototype" scoped beans

If I go back to the plain old listener pattern, only registered AND instantiated listeners are notified if something happens.

I think this should be done with the EventListener mechanism in Spring as well and Keep Things Simple for the application.

Comment From: vilmosnagy

Any news on this?

Comment From: snicoll

This looks like a Vaadin issue to me. The framework will request the bean that is target for a particular event and will request the context to provide an instance if that's needed. The core framework doesn't know anything about Vaadin's view management so there's nothing that really prevents what is happening.