spring-data-mongodb icon indicating copy to clipboard operation
spring-data-mongodb copied to clipboard

Publishing MongoMappingEvent invokes unrelated event listeners [DATAMONGO-2129]

Open spring-projects-issues opened this issue 7 years ago • 4 comments

lnevaril opened DATAMONGO-2129 and commented

Framework spring-data-mongodb provides possibility to register own listeners for the lifecycle events through extending generic AbstractMongoEventListener<T>. All lifecycle events are processed by SimpleApplicationEventMulticaster and corresponding listeners are invoked. For each lifecycle event all listeners extending AbstractMongoEventListener are invoked even though the generic type is different and the filtering is done in AbstractMongoEventListener instances method onApplicationEvent but it could be done in the higher level in the event multicaster through existing facility. This fact has significant performance impact on the database queries (in our application by order of magnitude for findAll operations). Spring framework offers possibility to determine the type of generic events correctly by implementing ResolvableTypeProvider, for example org.springframework.context.PayloadApplicationEvent implements it. Is there any reason to not to implement the ResolvableTypeProvider interface in the MongoMappingEvent to reduce the number of invoked listeners for all lifecycle events only to the desired ones? For example during the findAll operation on collection it would reduce the overhead of calling all listeners for each collection's object.

 

Consider following example:

  There are three simple classes User, Category and Item and for each domain object there is a repository. Moreover, there are AbstractMongoEventListeners for each domain object.  

@Component
public class UserMongoEventListener extends AbstractMongoEventListener<User> {

   @Override
   public void onAfterLoad(AfterLoadEvent<User> event) {
      super.onAfterLoad(event);
   }

   @Override
   public void onAfterConvert(AfterConvertEvent<User> event) {
      super.onAfterConvert(event);
   }

   @Override
   public void onBeforeConvert(BeforeConvertEvent<User> event) {
      super.onBeforeConvert(event);
   }

}

 

@Component
public class CategoryMongoEventListener extends AbstractMongoEventListener<Category> {

   @Override
   public void onAfterLoad(AfterLoadEvent<Category> event) {
      super.onAfterLoad(event);
   }

   @Override
   public void onAfterConvert(AfterConvertEvent<Category> event) {
      super.onAfterConvert(event);
   }

   @Override
   public void onBeforeConvert(BeforeConvertEvent<Category> event) {
      super.onBeforeConvert(event);
   }

}

  

@Component
public class ItemMongoEventListener extends AbstractMongoEventListener<Item> {

   @Override
   public void onAfterLoad(AfterLoadEvent<Item> event) {
      super.onAfterLoad(event);
   }

   @Override
   public void onAfterConvert(AfterConvertEvent<Item> event) {
      super.onAfterConvert(event);
   }

   @Override
   public void onBeforeConvert(BeforeConvertEvent<Item> event) {
      super.onBeforeConvert(event);
   }

}

 

Consider there are 1000 Item objects in the database and findAll method is called on this collection. Events AfterLoadEvent and AfterConvertEvent are published for each Item document.

  By default event multicasting is done by SimpleApplicationEventMulticaster. ResolvableType of MongoMappingEvent is org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent<?>, therefore, getApplicationListeners(event, type) returns all defined AbstractMongoEventListener instances (in fact it returns all listeners registered for Spring's ApplicationEvent).  

org.springframework.context.event.SimpleApplicationEventMulticaster


@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      Executor executor = getTaskExecutor();
      if (executor != null) {
         executor.execute(() -> invokeListener(listener, event));
      }
      else {
         invokeListener(listener, event);
      }
   }
}

  

This would result in 4000 unnecessary calls to listeners defined for different domain type since ResolvableType of MongoMappingEvent's does not provide information of the exact generic parameter type.

 

Is there any reason for this behavior that I do not understand? Possibly I could contribute with the pull request to extend MongoMappingEvent with the ResolvableTypeProvider.

 

Thank you


No further details from DATAMONGO-2129

spring-projects-issues avatar Nov 09 '18 09:11 spring-projects-issues

Oliver Drotbohm commented

I think this stems from the fact that AbstractMongoEventListener was created way before Spring Framework applied this intensive generics resolution for calling listener instances. Have you tried the annotation based model instead, i.e. registering an arbitrary Spring component with methods annotated with @EventListener? Does that show the same erroneous / superfluous invocations?

spring-projects-issues avatar Nov 09 '18 13:11 spring-projects-issues

lnevaril commented

Yes, we are using both approaches and both of them possesses the same behavior. We have custom events extending SpringApplicationEvent and listeners registered using @EventListener annotation and they are also invoked for each MongoMappingEvent.

spring-projects-issues avatar Nov 09 '18 14:11 spring-projects-issues

Oliver Drotbohm commented

Okay, that's helpful. I remember the ResolvableTypeProvider interface being introduced for us in particular as I was asking for it (see SPR-13069 for details) and I could swear I remember working on code that made use of this callback.

I'll see what I can do just changing the event types to implement PayloadApplicationEvent or at least ResolvableTypeProvider and keep you posted

spring-projects-issues avatar Nov 09 '18 14:11 spring-projects-issues

I'm also having the same problem, when extending AbstractMongoEventListener<T> the received events' source are of type T. This caused me some circular dependency issues, so trying to switch to @EventListener, the listening method (with a subclass of MongoMappingEvent<T> parameter) is now receiving events for any type and not just type T

Any updates on the issue?

origeva avatar Oct 02 '23 13:10 origeva