如何解决 Spring 中的双 bean 冲突
How to solve a double bean conflict in Spring
我正在尝试同时使用两个库,GraphQL 和 Jmix。
我使用 Intellij 的新项目向导(安装了 Jmix 插件)创建了 Jmix 项目,然后使用标准的 graphql-spring-boot-starter 将 GraphQL 添加到 Gradle。然后我写了一个模式和解析器 bean。
但是在启动期间,由于 WebSocket 端点 /subscriptions 在 Tomcat 上被注册了两次,所以抛出了异常。 (我尝试使用应用程序 属性 graphql.servlet.subscriptions.websocket.path 更改端点,但这不是问题所在。)
经过一些挖掘,我发现来自 jmix-ui-starter 的 类 GraphQLWebsocketAutoConfiguration from graphql-spring-boot-autoconfigure and VaadinAutoConfiguration 都在注册一个 ServerEndpointExporter bean,这是不应该发生的。
这是 graphql 的代码:
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(ServerContainer.class)
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
这里是 Jmix 的:
@Bean
public ServerEndpointExporter websocketEndpointDeployer() {
return new VaadinWebsocketEndpointExporter();
}
GraphQL 被标记为 ConditionalOnMissingBean,但在另一个之前注册,因此不会触发条件。
如何禁用这两个 bean 之一,或设置它们的优先级?
我通过完全禁用 GraphQL 的 websocket 服务设法解决了这个问题:
graphql.servlet.websocket.enabled = false
但是我想知道一般如何解决这种问题。
不幸的是,配置似乎有问题,因此您无法以 Spring-ish 方式“惯用地”执行某些操作。
我能想到的所有解决方案都是变通办法,解决这个问题的最好方法可能是在相应的团队中打开一个缺陷,这样他们就可以引入一个 属性 来禁用两种方式配置。好吧,也许他们已经这样做了,谁知道呢。
至于可能的解决方案:
使用属性:graphql.servlet.websocket.enabled=false
它将禁用 graphql 配置。您将能够自己重新定义所需的 bean。例如,如果需要,只定义:ServerEndpointRegistration
和 ServerEndpointExporter
bean。警告 - 我没有检查 graphql 库的源文件,也许这个 属性 在其他地方使用。
尝试重新定义您want/don不想加载的 bean 的 BeanDefinition。这可以通过 BeanFactoryPostProcessor 来完成:
@Component
public class SampleBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// find the bean definition of the bean you need and try to:
// - set "primary" on it
String bdName = ... here put the name of the bean you want to fine-tune
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(bdName);
beanDefinition.setPrimary(true);
// ... or ...
// - remove it altogether from the application context (bean factory)
((BeanDefinitionRegistry)beanFactory).removeBeanDefinition(bdName);
澄清一下 - bean 工厂 post 处理器在应用程序启动期间由 spring 调用,此时 bean 定义已经解析但在实际注入发生之前。
我正在尝试同时使用两个库,GraphQL 和 Jmix。
我使用 Intellij 的新项目向导(安装了 Jmix 插件)创建了 Jmix 项目,然后使用标准的 graphql-spring-boot-starter 将 GraphQL 添加到 Gradle。然后我写了一个模式和解析器 bean。
但是在启动期间,由于 WebSocket 端点 /subscriptions 在 Tomcat 上被注册了两次,所以抛出了异常。 (我尝试使用应用程序 属性 graphql.servlet.subscriptions.websocket.path 更改端点,但这不是问题所在。)
经过一些挖掘,我发现来自 jmix-ui-starter 的 类 GraphQLWebsocketAutoConfiguration from graphql-spring-boot-autoconfigure and VaadinAutoConfiguration 都在注册一个 ServerEndpointExporter bean,这是不应该发生的。
这是 graphql 的代码:
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(ServerContainer.class)
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
这里是 Jmix 的:
@Bean
public ServerEndpointExporter websocketEndpointDeployer() {
return new VaadinWebsocketEndpointExporter();
}
GraphQL 被标记为 ConditionalOnMissingBean,但在另一个之前注册,因此不会触发条件。
如何禁用这两个 bean 之一,或设置它们的优先级?
我通过完全禁用 GraphQL 的 websocket 服务设法解决了这个问题:
graphql.servlet.websocket.enabled = false
但是我想知道一般如何解决这种问题。
不幸的是,配置似乎有问题,因此您无法以 Spring-ish 方式“惯用地”执行某些操作。
我能想到的所有解决方案都是变通办法,解决这个问题的最好方法可能是在相应的团队中打开一个缺陷,这样他们就可以引入一个 属性 来禁用两种方式配置。好吧,也许他们已经这样做了,谁知道呢。
至于可能的解决方案:
使用属性:
graphql.servlet.websocket.enabled=false
它将禁用 graphql 配置。您将能够自己重新定义所需的 bean。例如,如果需要,只定义:ServerEndpointRegistration
和ServerEndpointExporter
bean。警告 - 我没有检查 graphql 库的源文件,也许这个 属性 在其他地方使用。尝试重新定义您want/don不想加载的 bean 的 BeanDefinition。这可以通过 BeanFactoryPostProcessor 来完成:
@Component
public class SampleBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// find the bean definition of the bean you need and try to:
// - set "primary" on it
String bdName = ... here put the name of the bean you want to fine-tune
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(bdName);
beanDefinition.setPrimary(true);
// ... or ...
// - remove it altogether from the application context (bean factory)
((BeanDefinitionRegistry)beanFactory).removeBeanDefinition(bdName);
澄清一下 - bean 工厂 post 处理器在应用程序启动期间由 spring 调用,此时 bean 定义已经解析但在实际注入发生之前。