Spring autoconfigurations, #ConditionalOnBean with a #Repository - spring

I have a starter module which expose a marker interface along with some repository:
interface AwesomeRepo
...
internal interface FakeRepository: Repository<JPAStub, String>, AwesomeRepo {
fun count(): Long
}
#Entity
class JPAStub(#Id val name: String)
#Configuration(proxyBeanMethods = false)
#ConditionalOnBean(EntityManagerFactory::class)
#AutoConfigureAfter(JpaRepositoriesAutoConfiguration::class)
#EnableJpaRepositories(basePackageClasses = [FakeRepository::class])
#EntityScan(basePackageClasses = [FakeRepository::class])
class AwesomePersistenceAutoConfiguration
In another module, I have an auto configuration which depends on the AwesomeRepo to instantiate the AwesomeApplicationService
#Configuration(proxyBeanMethods = false)
class AwesomeAutoConfiguration {
#Bean
#ConditionalOnMissingBean
#ConditionalOnBean(AwesomeRepo::class)
fun awesomeAppService(awesomeRepo: AwesomeRepo) =
AwesomeApplicationService(awesomeRepo)
I import both autoconfigure starters in a root project.
I observe:
AwesomeApplicationService cannot be instantiated because AwesomeRepo bean cannot be found
When enabling debug through debug=true:
AwesomeAutoConfiguration #awesomeAppService:
Did not match:
- #ConditionalOnBean (types: *****.AwesomeRepo; SearchStrategy: all) did not find any beans of type *******.AwesomeRepo(OnBeanCondition)
I tried adding #AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) to AwesomePersistenceAutoConfiguration and #AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE) to AwesomeAutoConfiguration. It did not change the issue
When I remove the #ConditionOnBean(AwesomeRepo::class), the AwesomeApplicationService is correctly instantiated with the repository and everything is fine.
Why does the #ConditionOnBean(AwesomeRepo::class) does not detect the AwesomeRepo bean?
EDIT: After more trials and errors, it seems order was causing the issue, applying accepted answer worked. If someone needs to go further there is a baseline of code illustrating the issue here: https://github.com/Nimamoh/spring-autoconfigurations-conditionalonbean-with-a-repository (accepted answer is on snic-answer branch)

AwesomeAutoConfiguration should be ordered after AwesomePersistenceAutoConfiguration so that the bean definitions for the repositories are processed before the condition kicks in.
There is a note in #ConditionalOnBean about this specifically:
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.
You can use #AutoConfigureAfter(AwesomePersistenceAutoConfiguration.class) on AwesomeAutoConfiguration to order things properly.

Related

#ConditionalOnBean not work for spring boot test

Hy everyone! I'm trying to solve the problem for a very long time.
There is very simple spring boot test
public class ApplicationTest {
#Test
void testContext() {
SpringApplication.run(Application.class);
}
}
And several beans...
#Service
#ConditionalOnBean(CommonService.class)
#RequiredArgsConstructor
public class SimpleHelper {
...
#Service
#RequiredArgsConstructor
#ConditionalOnBean(CommonFeignClient.class)
public class CommonService {
...
#FeignClient(
name = "CommonClient",
url = "localhost:8080"
)
public interface CommonFeignClient {
And the main class look as
#SpringBootApplication
#EnableFeignClients(clients = AnotherFeignClient.class)
public class Application {
When the spring application starts everything works ok. SimpleHelper does not created.
But in the spring boot test throw the exception:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'simpleHelper' defined in URL [...]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'foo.bar.CommonService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Please help, I don't understand what's going on anymore =)
Spring boot version is 2.3.0.RELEASE.
To use #ConditionalOnBean correctly, CommonService needs to be an auto-configuration class or defined as a bean by an auto-configured class rather than a service that's found by component scanning. This ensures that the bean on which CommonService is conditional has been defined before the condition is evaluated. The need for this is described in the annotation's javadoc:
The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.

How to properly configure multiple component scans and "use default filters = false"

I'm developing a "new application" that wants to leverage code in an existing application, but I am having trouble getting "contextLoads()" to pass in the new application.
This configuration doesn't work:
//This is the main common library B application class:
#Configuration
#ComponentScan(basePackages=["com.acme.cacheb.lib"])
#Import(CacheACommonLibConfig.class)
#EnableConfigurationProperties
class CacheBCommonLib {
}
//This is the Config class Imported above:
#Configuration
#ComponentScan(basePackages=["com.acme.cachea.lib"],
useDefaultFilters = false,
includeFilters = [#ComponentScan.Filter(type = FilterType.CUSTOM,
value = RescHandshakeTypeFilter.class)])
class CacheACommonLibConfig {
}
The error reported is an autowire failure:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'cacheA_RepoImpl': Unsatisfied dependency expressed through field 'cacheA_Repo'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.acme.cachea.lib.jpa.repository.ICacheA_Repository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I know my custom filter is matching everything I want (including that "missing" interface named ICacheA_Repository) and nothing I think I don't want. I suspect the error is because "useDefaultFilters = false" is in play and how the component scans are combined. If I do this
#ComponentScan(basePackages=["com.acme.cacheb.lib", "org.springframework"])
in the new main application class, the test runs longer before it fails reporting a different error, and I also get a fairly dire "Spring Warning"
** WARNING ** : Your ApplicationContext is unlikely to start due to a #ComponentScan of 'org.springframework'.
Any/All help greatly appreciated.
I got "context loads" to pass in the B app by abandoning "include filtering" (with use default filters = false) of the A app dependency jar and switching to "exclude filtering" on the component scan. But after I "actually" got it working, I'm not sure I really needed to switch to "exclude filtering". The reason I think that is because at the end of my journey, I started seeing the same couple of errors I first saw which is why the "Thymeleaf View Resolver" is now excluded from auto configuration. (If only I had tried that when the error first appeared, rather than totally disabling auto configuration...)
The B app main class configuration is now configured like this:
#Configuration
#ComponentScan(basePackages=["com.acme.cacheb.lib"])
#Import(CacheACommonLibConfig.class)
#EnableAutoConfiguration(exclude=[WebMvcAutoConfiguration.class, ThymeleafAutoConfiguration.class])
#EnableConfigurationProperties
#EntityScan('com.acme.cacha.lib.jpa.entity')
#EnableJpaRepositories('com.acme.cacha.lib.jpa.repository')
//#EnableScheduling
class CacheBCommonLib {
}
and the Imported config class is now configured like this:
#Configuration
#ComponentScan(basePackages=["com.acme.cacha.lib.msg.auth",
"com.acme.cacha.lib.msg.emp",
"com.acme.cacha.lib.msg.processor",
"com.acme.cacha.lib.jpa"],
excludeFilters = [#ComponentScan.Filter(type = FilterType.CUSTOM, value = CacheBExcludeCacheATypesFilter.class),
#ComponentScan.Filter(type = FilterType.REGEX,
pattern = "com.acme.cacha.lib.msg.processor.*"),
#ComponentScan.Filter(type = FilterType.REGEX,
pattern = "com.acme.cacha.lib.msg.auth.(sas|radius).*")
])
class CacheACommonLibConfig {
}

Spring Websocket TaskExecutor-beans displaces "applicationTaskExecutor"-bean

I have a Spring Boot 2.1.8 Application that uses #Async-Tasks. All #Async-Tasks used to be executed by an automatically configured ThreadPoolTaskExecutor-bean named applicationTaskExecutor.
What did I change?
With spring-boot-starter-websocket in the class path and a #EnableWebSocketMessageBroker configuration the applicationTaskExecutor-bean is gone and replaced by four beans with the names
clientInboundChannelExecutor,
clientOutboundChannelExecutor,
brokerChannelExecutor,
and messageBrokerTaskScheduler.
Spring logs to the console: AnnotationAsyncExecutionInterceptor : More than one TaskExecutor bean found within the context, and none is named 'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly as an alias) in order to use it for async processing: [clientInboundChannelExecutor, clientOutboundChannelExecutor, brokerChannelExecutor, messageBrokerTaskScheduler]
#Async-tasks are now executed by SimpleAsyncTaskExecutor.
Question
Why can't all beans co-exist? Why won't Spring create a applicationTaskExecutor-bean when spring-websockets is configured?
As #M. Deinum mentioned in the comments a #ConditionalOnMissingBean in TaskExecutionAutoConfiguration.java [0] results in the described behavior
I solved it by creating the bean by myself.
#ConditionalOnClass(ThreadPoolTaskExecutor.class)
#Configuration
public class ApplicationTaskExecutorBeanConfig {
#Lazy
#Bean(name = {APPLICATION_TASK_EXECUTOR_BEAN_NAME, DEFAULT_TASK_EXECUTOR_BEAN_NAME})
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
}
First I tried to prefer the bean from TaskExecutionAutoConfiguration.java by annotating my bean factory method with
#ConditionalOnMissingBean(name = {APPLICATION_TASK_EXECUTOR_BEAN_NAME, DEFAULT_TASK_EXECUTOR_BEAN_NAME})
but it didn't worked because my bean factory method gets invoked earlier than the one in TaskExecutionAutoConfiguration.java.
[0] https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java#L78
Thanks to #M. Deinum for your comment.

Spring-boot repository not being registered as a valid bean

Spring-boot does not seem to want to register my repository as a valid bean :( Here is the error I am seeing:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.frustrated.stats.MyRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
The underlying cause seems to be:
o.s.c.a.ClassPathBeanDefinitionScanner : Ignored because not a concrete top-level class: file [/Users/me/Code/my_app/my_service/build/classes/java/main/com/frustrated/stats/MyRepository.class]
Have I configured my application improperly somewhere?
Here are my files:
MyRepository.java:
package com.frustrated.stats;
#Repository
public interface MyRepository extends JpaRepository<StatsEvent, Long> {}
StatsEvent.java:
package com.frustrated.stats;
#Entity
public class StatsEvent { ... }
Application.java:
#SpringBootApplication
#ComponentScan(basePackages = { "com.frustrated" })
#EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
#EnableSpringDataWebSupport
public class Application extends SpringBootServletInitializer {
Here is my package structure:
com:
frustrated:
- Application.java
stats:
- MyRepository.java
- StatsEvent.java
Attempts to Debug
After trying a lot of different annotations, I thought it may be more productive to simply step through the registration process. I have traced the code, and it seems to be failing here:
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
It is the metadata.hasAnnotatedMethods(Lookup.class.getName()) that is false and causing my repository to not be instantiated as such.
It may also be of note that my StatsEvent is also ignored because:
o.s.c.a.ClassPathBeanDefinitionScanner : Ignored because not matching any filter: file [/Users/me/Code/my_app/my_service/build/classes/java/main/com/frustrated/stats/StatsEvent.class]
Any help would be greatly appreciated!
Judging by MyRepository extending JpaRepository, you are trying to use Spring Data JPA but your have excluded the auto-configuration for Hibernate and a DataSource. Unless you have some manual configuration for Hibernate or another JPA provider that you haven't shown, the support for JpaRepository will be disabled as it requires a JPA provider and a data source.
Try to add this on top of your Application.class
#EnableJpaRepositories(basePackages = "com.frustrated.stats.MyRepository")
#ComponentScan("com.frustrated.stats.service") // if you have it
#EntityScan("com.frustrated.stats.entity") // of your entities

spring-boot-starter can not find beans

I have a autoconfiguration class SFConfig that defines the following beans
#Bean
#ConditionalOnBean(value = SalesforceClientConfig.class)
SalesforceClient sfClient(SalesforceClientConfig sfConfig){
return SalesforceRestClient.from(sfConfig);
}
#Bean
//#ConditionalOnBean(value = Authentication.class)
SalesforceClientConfig sfClientConfig(Authentication sfAuthentication){
return DefaultSalesforceClientConfig.builder()
.authentication(sfAuthentication)
.mapper(mapper())
.build();
}
As evident sfClient bean should be created because SalesforceClientConfig is created. But it throws an exception:
Bean method 'sfClient' in 'SFConfig' not loaded because #ConditionalOnBean (types: com.ondeck.salesforceclient.SalesforceClientConfig; SearchStrategy: all) did not find any beans
This weird because this is an autoconfiguration class and it should find that bean. Any thoughts?
Here is how I have defined my autoconfiguration classes in the file:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ondeck.letter.config.SpringJpaDBConfig,\
com.ondeck.letter.config.SFConfig
According to Annotation Type ConditionalOnBean, it's recommended to use #ConditionalOnBean annotation in the auto configuration classes annotated with #EnableAutoConfiguration.
So probably you have not properly defined auto configuration class.

Resources