我们正在从 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
的初衷。
如果是这种情况,为什么不使用Gfsh、start 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 会更明显。
尽管您的问题陈述存在问题,但我可以推断以下内容:
显然,您正在使用">类路径组件扫描"(带有
@ComponentScan
注释)并且正在">按类型"自动连接; 这实际上可能是关键;稍后我将在下面回到这个问题。您正在 Bean 类字段(字段注入)或属性(setter 注入)上使用 Spring 的
@Autowired
注释,甚至可能是构造函数。此字段/属性(或构造函数参数)的类型肯定是
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) 首先创建了 GemFireCache
Bean,但 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