1

May I know how to implement a GemFire/Geode CacheListener in my Spring Boot application?

I want to detect delete and update in my "People" Region. I using org.springframework.data:spring-data-gemfire dependency in Maven. Do I need to include any annotation?

@SpringBootApplication  
@ClientCacheApplication(name = "AccessingDataGemFireApplication", logLevel = "error")
@EnableEntityDefinedRegions(basePackageClasses = People.class)
@EnableGemfireRepositories  
public class Application
{
....
}
John Blum
  • 7,381
  • 1
  • 20
  • 30
Jack
  • 89
  • 6

2 Answers2

2

Apparently there's still no annotation support for GemFire callbacks (CacheListener, CacheLoader, etc.) in the latest GA release of spring-data-gemfire. The JIRA ticket SGF-453 was created some time ago to implement this enhancement, you can add yourself as a watcher to get updates about the progress.

You can, however, manually associate a CacheListener to a region using the cache-listener xml element, as shown in the documentation. Or, yet another option, you can configure the listener directly through the API using RegionFactoryBean.setListeners.

As an example, the following class starts a local GemFire server with an embedded locator, creates a REPLICATED region named People and associates a custom CacheListener to it, which basically prints out a message every time an entry is created or deleted:

@EnableLogging
@EnableStatistics
@SpringBootApplication
@CacheServerApplication
public class GemFireServerApplication {

  public static void main(String[] args) {
    SpringApplication.run(GemFireServerApplication.class, args);
  }

  @EnableLocator
  @Profile("!clustered")
  @EnableManager(start = true)
  @Configuration
  static class LonerConfiguration { }

  @Bean("People")
  public ReplicatedRegionFactoryBean<?, ?> replicatedRegion(GemFireCache gemfireCache) {
    ReplicatedRegionFactoryBean<?, ?> replicatedRegion = new ReplicatedRegionFactoryBean<>();
    replicatedRegion.setCache(gemfireCache);
    replicatedRegion.setClose(false);
    replicatedRegion.setPersistent(false);
    replicatedRegion.setCacheListeners(new CacheListener[] { myCacheListener() });

    return replicatedRegion;
  }

  @Bean
  public CacheListener<?, ?> myCacheListener() {
    return new CacheListenerAdapter<String, String>() {
      @Override
      public void afterDestroy(EntryEvent<String, String> event) {
        System.out.println(String.format("AfterDestroy invoked for key %s !", event.getKey()));
        super.afterDestroy(event);
      }

      @Override
      public void afterCreate(EntryEvent<String, String> event) {
        System.out.println(String.format("AfterCreate invoked for key %s !", event.getKey()));
        super.afterCreate(event);
      }
    };
  }
}

Hope this helps. Cheers.

Juan Ramos
  • 1,421
  • 1
  • 8
  • 13
1

Since you are using the more convenient @EnableEntityDefinedRegions SDG annotation (which is recommended, especially for simple UC), then you do not explicitly define a Region bean definition (e.g. using SDG's ReplicationRegionFactoryBean or PartitionedRegionFactoryBean classes) as Juan Ramos did in his answer.

In this case, you can declare a RegionConfigurer to modify the "People" Region and then apply the same technique Juan did from his answer to supply the CacheListener.

For example:

@SpringBootApplication
@EnableEntityDefinedRegions(basePackageClasses = People.class)
@EnableGemfireRepositories(..)
class MySpringBootGemFireApplication {

  @Bean
  RegionConfigurer peopleRegionConfigurer(
      CacheListener<Long, Person> peopleRegionListener) {

    return new RegionConfigurer() {

      @Override
      public void configure(String beanName, 
          ClientRegionFactoryBean<Long, Person> regionFactory) {

        regionFactoryBean.setCacheListeners(
          new CacheListener[] { peopleRegionListener });
    };
  }

  @Bean
  CacheListenerAdapter<Long, Person> peopleRegionListener() {

    return new CacheListenerAdapter<>() {

        public void afterDestroy(EntryEvent<Long, Person> event) { ... }

        public void afterUpdate(EntryEvent<Long, Person> event) { ... }
    };
  }
}

NOTE: when using the Spring Boot, GemFire Starter (org.springframework.geode:spring-gemfire-starter), the @ClientCacheApplication annotation is definitely not required since SBDG auto-configures a ClientCache instance by default (see here).

NOTE: Additionally, if your application GemfireRepositories are in a sub-package below the main Spring Boot application class, you also do not need to explicitly declare the @EnableGemfireRepositories since SBDG auto-configures the SD Repository infrastructure for you (see here).

For more information on SDG Configurers, see here.

Hope this helps.

John Blum
  • 7,381
  • 1
  • 20
  • 30
  • When i try your solution, my compiler gave me this "The target type of this expression must be a functional interface" on the line "return (beanName, regionFactoryBean)". Since it is default method. I think Lambdas don't allow overriding default methods. Any way to solve this issue? – Jack Apr 14 '19 at 17:03
  • My apologies. Actually, it is because the `RegionConfigurer` interface (https://docs.spring.io/spring-data/geode/docs/current/api/org/springframework/data/gemfire/config/annotation/RegionConfigurer.html) is not a `FunctionalInterface`. The interface defines 2 default methods, 1 with the `ClientRegionFactoryBean` to define client Regions and another with the `PeerRegionFactoryBean` when defining server Regions. Therefore, you must be exact about which method you are "overriding", and as such, the _Lambda) does not work for this interface unlike other SDG provided `Configurers`. – John Blum Apr 15 '19 at 03:48
  • I corrected my example above. You can see another example of the `RegionConfigurer` in SDG's test suite, here (https://github.com/spring-projects/spring-data-geode/blob/master/src/test/java/org/springframework/data/gemfire/config/annotation/RegionConfigurerIntegrationTests.java#L185-L203). Technically, you only need to override 1 of the 2 methods depending on whether your application is a "client" or an actual "peer" in the cluster. Given you are using `@ClientCacheApplication`, then you would override the client method as my modified example shows above. – John Blum Apr 15 '19 at 04:00
  • Thank you so much. – Jack Apr 15 '19 at 23:19