11. Bootstrapping a Spring ApplicationContext in GemFire

11.1 Introduction

Normally, a Spring-based application will bootstrap GemFire using Spring Data GemFire's XML namespace. Just by specifying a <gfe:cache/> element in Spring Data GemFire configuration meta-data, a single, peer GemFire Cache instance will be created and initialized with default settings in the same JVM process as your application.

However, sometimes it is a requirement, perhaps imposed by your IT operations team, that GemFire must be fully managed and operated using the provided GemFire tool suite, such as with Gfsh. Using Gfsh, even though the application and GemFire will share the same JVM process, GemFire will bootstrap your Spring application context rather than the other way around. So, using this approach GemFire, instead of an application server, or a Java main class using Spring Boot, will bootstrap and host your application.

Keep in mind, however, that GemFire is not an application server. In addition, there are limitations to using this approach where GemFire Cache configuration is concerned.

11.2 Using GemFire to Bootstrap a Spring Context Started with Gfsh

In order to bootstrap a Spring application context in GemFire when starting a GemFire Server process using Gfsh, a user must make use of GemFire's Initalizer functionality. An Initializer can be used to specify a callback application that is launched after the Cache is initialized by GemFire.

An Initializer is specified within an initializer element using a minimal snippet of GemFire's native configuration meta-data inside a cache.xml file. The cache.xml file is required in order to bootstrap the Spring application context, much like a minimal snippet of Spring XML config is needed to bootstrap a Spring application context configured with component scanning (e.g. <context:component-scan base-packages="..."/>)

As of Spring Data GemFire 1.4, such an Initializer is already conveniently provided by the framework, the org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer. The typical, yet minimal configuration for this class inside GemFire's cache.xml file will look like the following:

<?xml version="1.0"?>
<!DOCTYPE cache PUBLIC  "-//GemStone Systems, Inc.//GemFire Declarative Caching 7.0//EN"
  "http://www.gemstone.com/dtd/cache7_0.dtd">

<cache>
  <initializer>
    <class-name>org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer</class-name>
    <parameter name="contextConfigLocations">
      <string>classpath:application-context.xml</string>
    </parameter>
  </initializer>
</cache>
		

The SpringContextBootstrappingInitializer class follows similar conventions as Spring's ContextLoaderListener class for bootstrapping a Spring context inside a Web Application, where application context configuration files are specified with the contextConfigLocations Servlet Context Parameter. In addition, the SpringContextBootstrappingInitializer class can also be used with a basePackages parameter to specify a comma-separated list of base package containing the appropriately annotated application components that the Spring container will search using component scanning and create Spring beans for:

<?xml version="1.0"?>
<!DOCTYPE cache PUBLIC  "-//GemStone Systems, Inc.//GemFire Declarative Caching 7.0//EN"
  "http://www.gemstone.com/dtd/cache7_0.dtd">

<cache>
  <initializer>
    <class-name>org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer</class-name>
    <parameter name="basePackages">
      <string>org.mycompany.myapp.services,org.mycompany.myapp.dao,...</string>
    </parameter>
  </initializer>
</cache>
		

Then, with a properly configured and constructed CLASSPATH along with the cache.xml file shown above specified as a command-line option when starting a GemFire Server in Gfsh, the command-line would be:

gfsh>start server --name=Server1 --log-level=config ...
			--classpath="/path/to/spring-data-gemfire-1.4.0.jar:/path/to/application/classes.jar"
			--cache-xml-file="/path/to/gemfire/cache.xml"
		

The application-context.xml can be any valid Spring context configuration meta-data including all the SDG namespace elements. The only limitation with this approach is that the GemFire Cache cannot be configured using the Spring Data GemFire namespace. In other words, none of the <gfe:cache/> element attributes, such as cache-xml-location, properties-ref, critical-heap-percentage, pdx-serializer-ref, lock-lease, etc can be specified. If used, these attributes will be ignored. The main reason for this is that GemFire itself has already created an initialized the Cache before the Initializer gets invoked. As such, the Cache will already exist and since it is a "Singleton", it cannot be re-initialized or have any of it's configuration augmented.

11.3 Lazy-Wiring GemFire Components

Spring Data GemFire already provides existing support for wiring GemFire components (such as CacheListeners, CacheLoaders or CacheWriters) that are declared and created by GemFire in cache.xml using the WiringDeclarableSupport class as described in Section 6.6.2, “Configuration using auto-wiring and annotations”. However, this only works when Spring does the bootstrapping (i.e. bootstraps GemFire). When your Spring application context is the one bootstrapped by GemFire, then these GemFire components go unnoticed since the Spring application context does not even exist yet! The Spring application context will not get created until GemFire calls the Initializer, which occurs after all the other GemFire components and configuration have already been created and initialized.

So, in order to solve this problem, a new LazyWiringDeclarableSupport class was introduced, that is, in a sense, Spring application context aware. The intention of this abstract base class is that any implementing class will register itself to be configured by the Spring application context created by GemFire after the Initializer is called. In essence, this give your GemFire managed component a chance to be configured and auto-wired with Spring beans defined in the Spring application context.

In order for your GemFire application component to be auto-wired by the Spring container, create a application class that extends the LazyWiringDeclarableSupport and annotate any class member that needs to be provided as a Spring bean dependency, similar to:

public static final class UserDataSourceCacheLoader extends LazyWiringDeclarableSupport implements CacheLoader<String, User> {

  @Autowired
  private DataSource userDataSource;

  ...
}
		

As implied by the CacheLoader example above, you might necessarily (although, rare) have defined both a Region and CacheListener component in GemFire cache.xml. The CacheLoader may need access to an application DAO, or perhaps Spring application context defined JDBC Data Source for loading "Users" into a GemFire Cache REPLICATE Region on start. Of course, one should be careful in mixing the different life-cycles of GemFire and the Spring Container together in this manner as not all use cases and scenarios are supported. The GemFire cache.xml configuration would be similar to the following (which comes from SDG's test suite):

<?xml version="1.0"?>
<!DOCTYPE cache PUBLIC  "-//GemStone Systems, Inc.//GemFire Declarative Caching 7.0//EN"
  "http://www.gemstone.com/dtd/cache7_0.dtd">

<cache>
  <region name="Users" refid="REPLICATE">
    <region-attributes initial-capacity="101" load-factor="0.85">
      <key-constraint>java.lang.String</key-constraint>
      <value-constraint>org.springframework.data.gemfire.repository.sample.User</value-constraint>
      <cache-loader>
        <class-name>org.springframework.data.gemfire.support.SpringContextBootstrappingInitializerIntegrationTest$UserDataStoreCacheLoader</class-name>
      </cache-loader>
    </region-attributes>
  </region>
  <initializer>
    <class-name>org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer</class-name>
    <parameter name="basePackages">
      <string>org.springframework.data.gemfire.support.sample</string>
    </parameter>
  </initializer>
</cache>