Search code examples
javaspringapplicationcontextsession-scope

Session-scoped bean leaks in wrong ApplicationContext


I have an application, that has 2 ApplicationContexts A and B, where A is the parent context of B. B overwrites some beans from A, but also uses some of its beans. For the frontend, I use Wicket and I have two Wicket applications AppA and AppB that use the respective Spring ApplicationContext.

The problem now arises with a Session-scoped bean sessionBean, for which I have 2 different implementations: A defines a factory method returning an instance of sessionBeanA, B has a factory method with same signature returning an instance of sessionBeanB. The user now opens AppB and in the backend, we get an instance of sessionBeanB, wherever we inject a sessionBean, as we would expect. However, if the user now leaves AppB and opens AppA (still with the same underlying session), we get an instance of sessionBeanB injected in A, because the SessionScope object still holds the previously created bean object.

How can I prevent that a session-scoped bean leaks in a different ApplicationContext? ApplicationContext A should not have to bother with beans from B...

Any help is highly appreciated.


Solution

  • It's possible to extend the SessionScope from Spring to also consider the ApplicationContext by using a CustomScopeConfigurer as follows:

    import org.springframework.beans.factory.config.CustomScopeConfigurer;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    
    public final class SessionScopeConfigurer extends CustomScopeConfigurer implements ApplicationContextAware {
      private final CustomSessionScope scope;
    
      public SessionScopeConfigurer() {
        scope = new CustomSessionScope();
        // Overwrite the session scope added by Spring
        addScope("session", scope);
      }
    
      @Override
      public void setApplicationContext(ApplicationContext applicationContext) {
        scope.setApplicationContextId(applicationContext.getId());
      }
    }
    

    where CustomSessionScope is:

    import org.springframework.beans.factory.ObjectFactory;
    import org.springframework.web.context.request.SessionScope;
    
    /**
     * Extends {@link SessionScope} by modifying all bean names, so that they are specific to their ApplicationContext.
     * This avoids session-scoped beans from context a leaking through context b.
     */
    public class CustomSessionScope extends SessionScope {
      private String applicationContextId;
    
      public void setApplicationContextId(String applicationContextId) {
        this.applicationContextId = applicationContextId;
      }
    
      @Override
      public Object get(String name, ObjectFactory<?> objectFactory) {
        return super.get(modify(name), objectFactory);
      }
    
      @Override
      public Object remove(String name) {
        return super.remove(modify(name));
      }
    
      @Override
      public void registerDestructionCallback(String name, Runnable callback) {
        super.registerDestructionCallback(modify(name), callback);
      }
    
      private String modify(String name) {
        // Attach ApplicationContextId to the bean name
        return name + applicationContextId;
      }
    }
    

    and you can use it in the Java Spring configuration class with:

    @Bean
    public static CustomScopeConfigurer configureSessionScope() {
      return new SessionScopeConfigurer();
    }