Gemfire NoSuchBeanDefinitionException Autowiring Cache (Spri



我们正在从 Gemfire 8.2.7 迁移到 9.2.1

作为 Gemfire 初创公司的一部分,我们利用 SpringContextBootstrappingInitializer 来初始化@Autowire缓存的 spring-beans。

迁移到 Gemfire 9.2.1(以及其他堆栈)时,相同的代码在服务器启动时失败,出现以下错误。

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

引起: org.springframework.beans.factory.NoSuchBeanDefinitionException: No 提供类型为"org.apache.geode.cache.Cache"的合格 bean: 预计至少有 1 个 Bean 符合自动连线候选条件。 依赖项注释: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

GemfireConfig 需要任何指针/更改吗?下面是我们的JavaConfig。

@Bean
public CacheFactoryBean gemfireCache() {
return new CacheFactoryBean();
}

看起来组件扫描在配置处理器之前启动。关于控制这种行为的任何想法?这最后被测试为在 Spring-Boot 1.4.6 (Spring- 4.3.8) 中工作,并通过@Depends选项得到解决 - 但只是想了解使用较新的 Spring 版本进行 bean 初始化的排序是否有任何根本性的变化。

@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) )

首先,让我给你一些提示,因为你上面的问题陈述有 3 个问题......

1)首先,您没有明确说明为什么或如何使用此处的o.s.d.g.support.SpringContextBootstrappingInitializer文档。

我只能假设这是因为您正在使用Gfsh启动GemFire服务器 使用以下命令...

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

您的cache.xml定义与此类似。 毕竟,这是使用SpringContextBootstrappingInitializer的初衷。

如果是这种情况,为什么不使用Gfshstart server命令--spring-xml-location选项。 例如:

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:...

这样,您不再需要提供cache.xml来声明SpringContextBootstrappingInitializer,以便在 GemFire JVM 进程中引导 Spring 容器。 您只需使用--spring-xml-location选项,并在启动服务器时将 SDG 放在服务器的类路径上。

2)其次,您正在将GemFireCache引用注入到哪种类型的应用程序组件/Bean中并不明显(例如区域或其他应用程序组件类,如DAO等)。 提供一段代码,显示您如何注入Cache引用,即使用@Autowired注释的注入点会很有帮助。 例如:

@Service
class MyService {
@Autowired
private Cache gemfireCache;
...
}

3) 如果您包含完整的堆栈跟踪而不仅仅是NoSuchBeanDefinitionException消息,#2 会更明显。

尽管您的问题陈述存在问题,但我可以推断以下内容:

  1. 显然,您正在使用">类路径组件扫描"(带有@ComponentScan注释)并且正在">按类型"自动连接; 这实际上可能是关键;稍后我将在下面回到这个问题。

  2. 您正在 Bean 类字段(字段注入)或属性(setter 注入)上使用 Spring 的@Autowired注释,甚至可能是构造函数。

  3. 此字段/属性(或构造函数参数)的类型肯定是org.apache.geode.cache.Cache

继续前进...

一般来说,Spring首先会遵循依赖顺序。 也就是说,如果 A 依赖于 B,那么 B 必须在 A 之前创建并在 A 之后销毁。 通常,春天会并且能够毫无意外地尊重这一点。

除了"依赖顺序"Bean 创建和满足 bean 之间的依赖关系(包括@DependsOn注释)之外,Bean 创建的顺序定义非常松散。

有几个因素会影响它,例如"注册顺序"(即声明 Bean 定义的顺序,这对于 XML 中定义的 bean 尤其如此)、"导入顺序"(在@Configuration类上使用@Import注释时)、Java 反射(包括在@Configuration类中声明@Bean定义)等。 配置组织绝对很重要,不应掉以轻心。

这就是为什么我不是">类路径组件扫描"的大力支持者的一个原因。 虽然这可能很方便,但IMO最好在您的配置和配置组织中更加"明确",除了其他不明显的限制之外,这里还概述了原因。 在最坏的情况下,您绝对应该限制扫描范围。

具有讽刺意味的是,您排除/过滤了实际上可以帮助您的组织问题的 1 件事......@Configuration型组件:

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

注意:给定排除,您确定没有排除包含CacheFactoryBean定义的 1@Configuration类吗? 我想不是,因为你说这在包含@DependsOn注释后有效。

显然,您的某些应用程序组件(??)和类型为o.a.g.cache.Cache(使用@Autowired)的bean之间定义了依赖关系,但是Spring未能解决它。

我的想法是,Spring 无法解决Cache依赖关系,因为 1) GemFire 缓存 Bean 尚未创建,2) Spring 找不到所需类型的适当 Bean 定义(即o.a.g.cache.Cache) 将解决依赖关系并强制首先创建 GemFireCache,或 3) 首先创建了 GemFireCacheBean,但 Spring 无法将类型解析为o.a.g.cache.Cache

我以前遇到过这两种情况,我并不完全清楚每种情况何时发生,因为我还没有追踪到这一点。 我只是简单地纠正了它并继续前进。 我注意到它与版本相关。

有几种方法可以解决此问题。

如果问题是后面的,3),那么只需将依赖项声明为类型o.a.g.cache.GemFireCache就可以解决问题。 因此,例如:

@Repository
class MyDataAccessObject {
@Autowired
private GemFireCache gemfireCache;
...
}

这样做的原因是o.s.d.g.CacheFactoryBean类的getObjectType()方法返回一个类类型,该类类型通常扩展o.a.g.cache.GemFireCache。 这是设计使然,因为o.s.d.g.client.ClientCacheFactoryBean扩展了o.s.d.g.CacheFactoryBean,尽管如果我创建了这些类,我可能不会那样做。 但是,这与 GemFire 中的实际缓存类型o.a.g.internal.cache.GemFireCacheImpl这一事实是一致的,这间接实现了o.a.g.cache.Cache接口和o.a.g.cache.client.ClientCache接口。

如果你的问题是前者(1)+ 2),这有点棘手),那么我建议你采用更智能的配置组织,按关注点分开。 例如,您可以使用以下命令封装您的 GemFire 配置:

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

然后,您的应用程序组件(其中一些依赖于 GemFire 组件)可以定义:

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

通过导入GemFireConfiguration,您可以确保首先创建(实例化、配置和初始化)GemFire 组件/bean。

在拥有大量应用程序组件(服务、DAO 等)的情况下,您甚至可以在ApplicationConfiguration类级别使用更有针对性的、有限的">类路径组件扫描"。

然后,你可以让你的主 Spring 引导应用程序类驱动所有这些:

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

关键是,您可以随心所欲地细化。 我喜欢按关注点封装配置,并清楚地组织配置(使用导入)以反映我希望创建(构造、配置和初始化)组件的顺序。

老实说,我基本上按照依赖项的顺序组织我的配置。 如果我的应用程序最终依赖于数据存储,并且没有该数据存储就无法运行,那么它就会确保首先初始化,否则,启动应用程序的意义何在。

最后,您始终可以依靠@DependsOn注释,就像您所做的那样,以确保 Spring 将在期望它的组件之前创建组件。

基于@DependsOn注释解决了您的问题的事实,那么我会说这是一个组织问题,属于我上面概述的 1)/2) 类别。

我将更深入地研究这个问题,并用我发现的内容在评论中回应我的答案。

希望这有帮助!

-John

最新更新