0

I have this class in a library project:

@ConditionalOnMissingBean(name = "myServiceActivator")
@Component(value = "myServiceActivator")
public class MyServiceActivator {

    @ServiceActivator(inputChannel = "SomeChannel")
    public void handleApplicationEvent(@Payload Object object) {
        // ...
    }

}

And in a project where I have the library as dependency I have:

@Component(value = "myServiceActivator")
public class ChildServiceActivator {

    @ServiceActivator(inputChannel = "SomeChannel")
    public void handleApplicationEvent(@Header("SomeHeader") String header, @Payload Object object) {
        // Do something else
    }
}

And I'm getting:

Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'myServiceActivator' for bean class [com.company.project.domain.integration.ChildServiceActivator] conflicts with existing, non-compatible bean definition of same name and class [com.company.commons.domain.integration.MyServiceActivator]

I'd expect @ConditionalOnMissingBean to skip creation of MyServiceActivator as per here, here and actually many more. Why doesn't it and how do I only create an instance of ChildServiceActivator?

Hasan Can Saral
  • 2,950
  • 5
  • 43
  • 78
  • 1
    `@ConditionalOnMissingBean` is not designed for your case, that is mentioned in javadoc: "it is strongly recommended to use this condition on auto-configuration classes only" – Andrey B. Panfilov Sep 01 '22 at 16:13
  • 1
    @AndreyB.Panfilov This worked for me. I believe that what I'm trying to do is also a valid (maybe not recommended) usage, but not sure as to why it doesn't work. If you could write it as an answer, I'd be happy to accept. Thanks. – Hasan Can Saral Sep 02 '22 at 07:06
  • yeap, sometimes it does work, however those observations are based on particular environment (java version, operating system, how spring-boot traverses dependency graph, etc), but, in general, the purpose of `@ConditionalOnMissingBean` is to support auto-configurations - that is clearly stated in javadoc. Using `@ConditionalOnMissingBean` outside auto-configuration is like playing improved russian roulette with five rounds in a revolver. – Andrey B. Panfilov Sep 02 '22 at 07:21

2 Answers2

0

If I understood you correctly, you want to use different implementations of ServiceActivator in various projects or have the ability to override the behavior of the default MyServiceActivator provided by your library.

Messing up with the same bean names or using @ConditionalOnMissingBean for this case may not be the most straightforward solution. And it also doesn't look good from the architectural point of view.

One possible solution would be introducing a common interface for these two classes, but this would, in return, require the unifying of parameters for the handleApplicationEvent method:

public interface ServiceActivator {
    void handleApplicationEvent(String header, Object object);
}

@Component
public class MyServiceActivator implements ServiceActivator {

    @Override
    @ServiceActivator(inputChannel = "SomeChannel")
    public void handleApplicationEvent(@Header("SomeHeader") String header, @Payload Object object) {
        // ...
    }

}

@Component
public class ChildServiceActivator implements ServiceActivator {

    @Override
    @ServiceActivator(inputChannel = "SomeChannel")
    public void handleApplicationEvent(@Header("SomeHeader") String header, @Payload Object object) {
        // Do something else
    }
}

With such an approach, you would be able to benefit from using @Qualifier during the auto wiring:

@Autowired
@Qualifier("childServiceActivator")
private ServiceActivator childServiceActivator;

Besides that, you would be able to define the primary bean in your configuration:

@Configuration
public class ServiceActivatorConfig {
    
    @Bean
    @Primary
    public ServiceActivator serviceActivator() {
        return new ChildServiceActivator();
    }
}

Another possible solution is to move the bean initialization to a separate configuration file in your library project instead of putting @Component on MyServiceActivator, which you would be able to import or exclude based on your needs.

Also, you maybe have considered it, but it might be worth mentioning the third option for this case – using @ConditionalOnProperty and enabling your beans based on the property value. From my point of view, it might be the optimal solution for your case, since it won't require any changes to the method signature.

@ConditionalOnProperty(value = "service-activator.provider", havingValue = "my")
@Component(value = "myServiceActivator")
public class MyServiceActivator {

    @Override
    @ServiceActivator(inputChannel = "SomeChannel")
    public void handleApplicationEvent(@Payload Object object) {
        // ...
    }

}

@ConditionalOnProperty(value = "service-activator.provider", havingValue = "child")
@Component(value = "myServiceActivator")
public class ChildServiceActivator {

    @Override
    @ServiceActivator(inputChannel = "SomeChannel")
    public void handleApplicationEvent(@Header("SomeHeader") String header, @Payload Object object) {
        // Do something else
    }
}
  • They do not impement the same interface and their handle methods are different. And I cannot put @ConditionalInProperty on library project, that’d break a lot of projects. – Hasan Can Saral Sep 01 '22 at 17:26
  • @HasanCanSaral Using `@ConditionalOnProperty(value = "service-activator.provider", havingValue = "my", matchIfMissing = true)` wouldn't be an option for you? It won't require changes to existing projects, since it would be enabled by default, only for those where you want to disable this activator service. – Alexander Golovnya Sep 01 '22 at 17:50
  • Removing `@Component` and defining it as a `@Bean` in a `@Configuration` class worked for me. I think what you describe would also work, however, I also think that it's against what Java configuration is trying to achieve, creating a property solely for configuring Spring Beans, i.e. configuring Beans with external files. – Hasan Can Saral Sep 02 '22 at 07:05
  • @HasanCanSaral Great, I'm glad that the solution worked for you. Regarding using properties for configuring Spring Beans, I have thought the same way before. But such kinds of feature flags allow you to build extremely flexible and highly configurable projects. It can be life-saving when you work on projects in highly regulated environments where making a minor release because of a small change may require a bunch of time and effort. – Alexander Golovnya Sep 02 '22 at 10:05
  • It's worth mentioning, that it makes sense to use when you have an external configuration server like Spring Cloud Config or Kubernetes ConfigMaps and Secrets, or an external feature flag provider like LaunchDarkly. Highly recommend checking it out — it can bring the management and configuration of your project to a new level. – Alexander Golovnya Sep 02 '22 at 10:05
0

Removing

@ConditionalOnMissingBean(name = "myServiceActivator")
@Component(value = "myServiceActivator")

and moving the definition of the @Bean to a @Configuration file as:

@Configuration
public class ServiceActivatorConfiguration{
    
    @Bean
    @ConditionalOnMissingBean(name = "myServiceActivator")
    public ServiceActivator serviceActivator() {
        return new MyServiceActivator();
    }
}

and overriding this configuration in the child project:

@Configuration
public class ChildServiceActivatorConfiguration{
    
    @Bean
    public ServiceActivator serviceActivator() {
        return new ChildServiceActivator();
    }
}

resolved the issue, as suggested here.

Hasan Can Saral
  • 2,950
  • 5
  • 43
  • 78