Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save robertoschwald/ba051f592e539d69aafd33d5e4ef5de4 to your computer and use it in GitHub Desktop.
Save robertoschwald/ba051f592e539d69aafd33d5e4ef5de4 to your computer and use it in GitHub Desktop.
Using Grails 4 with Spring-Session, and single-instance Hazelcast for Session-Replication and Hibernate 2nd Level cache

Goals

  • Use Spring-Session-Hazelcast to replicate Http Session between Application Nodes
  • Use Hazelcast Hibernate as Hibernate 2nd Level cache
  • Use only one single Hazelcast instance auto-generated by Spring Boot
  • Support Grails mutable Session objects (Flash messages, etc.)

The problem

  • Spring-Boot automatically configures a 'hazelcastInstance' bean if Hazelcast dependency is found.
  • We can configure hazelcast-hibernate to use an existing HazelcastInstance with hibernate.cache.hazelcast.instance_name config key.
  • Unfortunately, Grails instantiates the hibernateDatastore bean before Spring-Boot generates the hazelcastInstance bean.
  • Due to this, hazelcast-hibernate generates its own Hazelcast Instance, so you have 2 Hazelcast instances running in your application

The Solution

Ensure the Spring-Boot generated hazelcastInstance bean is generated before the Grails hibernateDatastore bean.

resources.groovy:

// Ensure Spring-Boot hazelcastInstance is generated before hibernateDatastore
// See also HazelcastHibernateDatastoreBeanPostProcessor
BeanDefinition hibernateDatastoreBeanDefinition = getBeanDefinition('hibernateDatastore')
  if (hibernateDatastoreBeanDefinition) {
    def dependsOnList = ['hazelcastInstance'] as Set
    if (hibernateDatastoreBeanDefinition.dependsOn?.length > 0) {
      dependsOnList.addAll(hibernateDatastoreBeanDefinition.dependsOn)
    }
    hibernateDatastoreBeanDefinition.dependsOn = dependsOnList as String[]
  }

HazelcastHibernateDatastoreBeanPostProcessor.groovy

/**
 * BeanPostProcessor to set Hibernate Hazelcast Instance name.
 * This PostProcessor sets the {@link HazelcastHibernateDatastoreBeanPostProcessor#HIBERNATE_HAZELCAST_INSTANCE_NAME_KEY }
 * to the name taken from hazelcastInstance bean as SystemProperty.
 * This way, hazelcast-hibernate uses the same instance as the Spring-Boot created hazelcastInstance.
 */
@Slf4j
@CompileStatic
@Component
class HazelcastHibernateDatastoreBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, PriorityOrdered {
  private final static String HIBERNATE_HAZELCAST_INSTANCE_NAME_KEY = "hibernate.cache.hazelcast.instance_name"

  private ApplicationContext applicationContext
  private int order = LOWEST_PRECEDENCE - 2

  @Override
  Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    log.trace("*** Postprocess {}", beanName)
    if (bean instanceof HazelcastInstance){
      String hazelcastInstanceName = applicationContext.getBean("hazelcastInstance", HazelcastInstance)?.name
      log.info("Setting hibernate.cache.hazelcast.instance_name to $hazelcastInstanceName")
      System.setProperty(HIBERNATE_HAZELCAST_INSTANCE_NAME_KEY, hazelcastInstanceName)
    }
    return bean
  }

  @Override
  int getOrder() {
    return order
  }

  @Override
  void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext
  }
}

Spring-Session and Grails mutable objects in Session

Mutable objects in session are considered bad practice, but Grails4 uses them at least in:

  • Flash Messages
  • Form Tokens

To workaround this, we reconfigure the Hazelcast sessionRepository to re-save session keys on getAttribute(). See also: spring-projects/spring-session#177

@Slf4j
@CompileStatic
@Configuration
class HazelcastSymInstanceConfiguration {

 /**
  * Reconfigure SessionRepository to re-save all session keys which have been obtained using getAttribute().
  * This way we ensure mutable Session objects are updated coherently in the Hazelcast cache.
  * Mutable objects in session are considered bad practice, but Grails uses them at least in:
  *  - Flash Messages
  *  - Form Tokens
  * {@see https://github.com/spring-projects/spring-session/issues/177}
  *
  * @return Customizer
  */
 @Bean
 SessionRepositoryCustomizer<HazelcastIndexedSessionRepository> customize() {
   return new SessionRepositoryCustomizer<HazelcastIndexedSessionRepository>() {
     @Override
     void customize(HazelcastIndexedSessionRepository sessionRepository) {
       log.info("Re-Configuring SessionRepositoryCustomizer to re-save session keys on getAttribute")
       sessionRepository.setFlushMode(FlushMode.ON_SAVE)
       sessionRepository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE)
     }
   }
 }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment