Tobia
Tobia

Reputation: 18811

How to solve a double bean conflict in Spring

I'm trying to use two libraries together, GraphQL and Jmix.

I created the Jmix project using Intellij's new project wizard (with the Jmix plugin installed) then I added GraphQL to Gradle using the standard graphql-spring-boot-starter. Then I wrote a schema and the resolver beans.

But during startup, an exception is thrown because the WebSocket endpoint /subscriptions is being registered twice on Tomcat. (I tried changing the endpoint by using the application property graphql.servlet.subscriptions.websocket.path, but that is not the issue.)

After some digging, I found that the classes GraphQLWebsocketAutoConfiguration from graphql-spring-boot-autoconfigure and VaadinAutoConfiguration from jmix-ui-starter are both registering a ServerEndpointExporter bean, which is not supposed to happen.

Here is graphql's code:

@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(ServerContainer.class)
public ServerEndpointExporter serverEndpointExporter() {
   return new ServerEndpointExporter();
}

and here Jmix's:

@Bean
public ServerEndpointExporter websocketEndpointDeployer() {
    return new VaadinWebsocketEndpointExporter();
}

GraphQL's is marked as ConditionalOnMissingBean, but is registered before the other, so the conditional is not triggered.

How can I disable one of these two beans, or set their priority?

I managed to work around the issue by disabling GraphQL's websocket service entirely:

graphql.servlet.websocket.enabled = false

But I would like to know how to solve this kind of problem in general.

Upvotes: 0

Views: 744

Answers (1)

Mark Bramnik
Mark Bramnik

Reputation: 42461

Unfortunately, It looks like the configurations are buggy, so there is no something you can do "idiomatically" in a Spring-ish way.

All the solutions that I can think of are rather workarounds, and probably the best way to solve this is opening a defect on the corresponding teams so that they could introduce a property which will allow to disable the Configuration in both ways. Well, maybe they've already done that, who knows.

As for the possible solutions:

  1. Use the property: graphql.servlet.websocket.enabled=false It will disable the graphql configuration. And you'll be able to redefine the beans you need by yourself. For example, define only: ServerEndpointRegistration and ServerEndpointExporter bean if you need them. Caveat - I haven't checked the source files of graphql library, maybe this property is used elsewhere.

  2. Try to redefine the BeanDefinition of the bean you want/don't want to load. This can be done with 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);

Just to clarify - bean factory post processors are called by spring during the application startup when the bean definitions are already resolved but before the actual injection takes place.

Upvotes: 1

Related Questions