2

We are migrating from Gemfire 8.2.7 to 9.2.1

As part of Gemfire startup, we leverage SpringContextBootstrappingInitializer to initialize the spring-beans which @Autowire the Cache.

The same code when migrated to Gemfire 9.2.1 (along with the other stack) is failing on server startup with below error.

Gemfire 8.2.7 --> Gemfire 9.2.1
Spring-data-Gemfire 1.8.4 --> 2.0.2
Spring-Boot 1.4.7 --> 2.0.0.M7
Spring --> 5.0.2

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.apache.geode.cache.Cache' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Any pointers / changes required for GemfireConfig? Below is our JavaConfig.

@Bean
public CacheFactoryBean gemfireCache() {

    return new CacheFactoryBean();
}

Looks like the ComponentScan is kicking in prior to Configuration processor. Any idea on controlling this behavior? This was lasted tested to work in Spring-Boot 1.4.6 (Spring- 4.3.8) and gets resolved with a @Depends option - but just wanted to understand if there are any fundamental changes with the ordering of bean initialization with newer Spring version.

@Configuration
@EnableAutoConfiguration(exclude = { HibernateJpaAutoConfiguration.class, BatchAutoConfiguration.class })
@Import(value = { GemfireServerConfig.class, JpaConfiguration.class, JpaConfigurableProperties.class })
@ComponentScan(basePackages = "com.test.gemfire", excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class) )
Jebuselwyn Martin
  • 543
  • 1
  • 5
  • 18

1 Answers1

2

To begin, let me give you some tips since there are 3 issues with your problem statement above...

1) First, you have not made it clear why or how you are using the o.s.d.g.support.SpringContextBootstrappingInitializer Docs here.

I can only assume it is because you are launching your GemFire servers with Gfsh using the following command...

gfsh> start server --name=MyServer --cache-xml-file=/path/to/cache.xml ...

Where your cache.xml is defined similar to this. After all, this was the original intent for using the SpringContextBootstrappingInitializer.

If this is the case, why not use the Gfsh, start server command, --spring-xml-location option instead. For example:

gfsh> start server --name=MyServer --spring-xml-location=/by/default/a/classpath/to/applicationContext.xml --classpath=/path/to/spring-data-gemfire-2.0.2.RELEASE.jar:...

By doing so, you no longer need to provide cache.xml just to declare the SpringContextBootstrappingInitializer in order to bootstrap a Spring container inside the GemFire JVM process. You can simply use the --spring-xml-location option and put SDG on the server's classpath when starting the server.

2) Second, it is not apparent what type of application component/bean you are injecting a GemFire Cache reference into (e.g. a Region or another application component class, like a DAO, etc). Providing a snippet of code showing how you injected the Cache reference, i.e. the injection point using the @Autowired annotation would have been helpful. For example:

@Service
class MyService {

  @Autowired
  private Cache gemfireCache;

  ...
}

3) #2 would have been more apparent if you included the full stack trace rather than just the NoSuchBeanDefinitionException message.

Despite the issues with your problem statement, I can infer the following:

  1. Clearly, you are using "classpath component scanning" (with the @ComponentScan annotation) and are auto-wiring "by type"; which maybe key actually; I will come back to this later below.

  2. You are using Spring's @Autowired annotation on a bean class field (field injection) or property (setter injection), maybe even a constructor.

  3. The type of this field/property (or constructor parameter) is definitely org.apache.geode.cache.Cache.

Moving on...

In general, Spring will follow dependency order first and foremost. That is, if A depends on B, then B must be created before and destroyed after A. Typically, Spring will and can honor this without incident.

Beyond "dependency order" bean creation and satisfying dependencies between beans (including with the @DependsOn annotation), the order of bean creation is pretty loosely defined.

There are several factors that can influence it, such as "registration order" (i.e. the order in which bean definitions are declared, which is particularly true for beans defined in XML), "import order" (when using the @Import annotation on @Configuration classes), Java reflection (includes @Bean definitions declared in @Configuration classes), etc. Configuration organization is definitely important and should not be taken lightly.

This is 1 reason why I am not a big proponent of "classpath component scanning. While it may be convenient, it is always better, IMO, to be more "explicit" in your configuration, and the organization of your configuration, for reasons outlined here in addition to other non-apparent limitations. At worst, you should definitely be limiting the scope of the scan.

Ironically, you excluded/filtered the 1 thing that could actually help your organizational concerns... components of type @Configuration:

... excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)

NOTE: given the exclusion, are you certain you did not exclude the the 1 @Configuration class containing your CacheFactoryBean definition? I suppose not since you say this worked after including the @DependsOn annotation.

Clearly there is a dependency defined between some application component of yours (??) and a bean of type o.a.g.cache.Cache (using @Autowired), yet Spring is failing to resolve it.

My thinking is, Spring cannot resolve the Cache dependency because 1) the GemFire cache bean either has not been created yet and 2) Spring cannot find an appropriate bean definition of the desired type (i.e. o.a.g.cache.Cache) in your configuration that would resolve the dependency and force the GemFire Cache to be created first, or 3) the GemFire Cache bean has been created first but Spring is unable to resolve the type as o.a.g.cache.Cache.

I have encountered both scenarios before and it is not exactly clear to me when each scenario happens because I simply have not traced this through yet. I have simply corrected it and moved on. I have noticed that it is version related though.

There are several ways to solve this problem.

If the problem is the later, 3), then simply declaring your dependency as type o.a.g.cache.GemFireCache should resolve the problem. So, for example:

@Repository
class MyDataAccessObject {

  @Autowired
  private GemFireCache gemfireCache;

  ...
}

The reason for this is because the o.s.d.g.CacheFactoryBean class's getObjectType() method returns a Class type generically extending o.a.g.cache.GemFireCache. This was by design since o.s.d.g.client.ClientCacheFactoryBean extends o.s.d.g.CacheFactoryBean, though I probably would not have done it that way if I had created these classes. However, it is consistent with the fact that the actual cache type in GemFire is o.a.g.internal.cache.GemFireCacheImpl which indirectly implements both the o.a.g.cache.Cache interface as well as the o.a.g.cache.client.ClientCache interface.

If your problem is the former (1) + 2), which is a bit trickier), then I would suggest you employ a smarter organization of your configuration, separated by concern. For example, you can encapsulate your GemFire configuration with:

@Configuration
class GemFireConfiguration {
  // define GemFire components (e.g. CacheFactoryBean) here
}

Then, your application components, where some are dependent on GemFire components, can be defined with:

@Configuration
@Import(GemFireConfiguration.class)
class ApplicationConfiguration {
  // define application beans, including beans dependent on GemFire components
}

By importing the GemFireConfiguration you are ensuring the GemFire components/beans are created (instantiated, configured and initialized) first.

You can even employ more targeted, limited "classpath component scanning" at the ApplicationConfiguration class-level in cases where you have a large number of application components (services, DAO, etc).

Then, you can have your main, Spring Boot application class drive all this:

@Configuration
@Import(ApplicationConfiguration.class)
class MySpringBootApplication {

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

The point is, you can be as granular as you choose. I like to encapsulate configuration by concern and clearly organize the configuration (using imports) to reflect the order in which I want my components created (constructed, configured and initialized).

Honestly, I basically organize my configuration in the order of dependencies. If my application ultimately depends on a data store and cannot function without that data store, then it makes since to ensure that is initialized first, otherwise, what is the point of starting the application.

Finally, you can always rely on the @DependsOn annotation, as you have appropriately done, to ensure that Spring will create the component before the component that expects it.

Based on the fact that the @DependsOn annotation solved your problem, then I would say this is an organizational problem and falls under the 1) / 2) category I outlined above.

I am going to dig into this a bit deeper and respond to my answer in comments with what I find.

Hope this helps!

-John

John Blum
  • 7,381
  • 1
  • 20
  • 30