#ConditionalOnBean not work for spring boot test - spring

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.

Related

#WebMvcTest with #Import does not work. Test context always asks for #Repository beans

Using Spring Boot 2.7.3 I can not create a simple integration test for my API using #WebMvcTest.
Here is my setup:
// GameServerApplicationTests.kt
#SpringBootTest
class GameServerApplicationTests {
#Test
fun contextLoads() { }
}
// CraftService.kt
#Service
class CraftService {
fun getAll(): List<String> {
return listOf("foo", "bar")
}
}
// CraftApiTest.kt
#WebMvcTest
#Import(value = [CraftService::class])
class CraftApiTest {
#Autowired
private lateinit var testRestTemplate: TestRestTemplate
#Test
fun `should do accept craft all endpoint`() {
val response = testRestTemplate.getForEntity("/craft/all", String::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
}
}
When I run the test I see this exception:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'itemRepository' defined in com.gameserver.item.ItemRepository defined in #EnableJpaRepositories declared on GameServerApplication: Cannot create inner bean '(inner bean)#3fba233d' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#3fba233d': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
I have no idea why it is looking for the itemRepository bean at all. I never asked for that.
I then added this
#WebMvcTest
#ComponentScan(excludeFilters = [ComponentScan.Filter(Repository::class)]) // <<
#Import(value = [CraftService::class])
Which resulted in this exception:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'playerRepository' defined in com.gameserver.player.PlayerRepository defined in #EnableJpaRepositories declared on GameServerApplication: Cannot create inner bean '(inner bean)#30c1da48' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#30c1da48': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
Which confuses me even more. I explictly excluded all #Repository beans - but it just skipped ItemRepository and then asked for PlayerRepository now.
I am totally lost and have no idea why I am not able to setup a simple integration test for my API endpoint.
EDIT #1:
Other tests run just fine:
EDIT #2:
I tried to use a #Configuration bean for #Import.
// CraftApiTestConfiguration
#Configuration
class CraftApiTestConfiguration {
#Bean
fun getCraftService(): CraftService {
return CraftService()
}
}
// CraftApiTest.kt
#WebMvcTest
#Import(CraftApiTestConfiguration::class)
class CraftApiTest { // ... }
That did not help either. It just gave me the second exception mentioned above (the one asking for playerRepository)
I'll try to answer although without seeing the actual code it might not be correct.
So #WebMvcTest loads a "slice" of your application with all the beans annotated with #RestControllers. It doesn't load #Service or #Repository annotated beans.
When you run the test with #WebMvcTest annotation it will load all the controllers, and if, by accident the controller references others than the reference on the service (here I can't say for sure what it is), you might end up loading the stuff that you don't actually need.
Now when you use #WebMvcTest there are two things you can/should do:
Work with MockMvc instead of rest template that queries a web server, its not a full-fledged web layer test anyway.
Try using #WebMvcTest with your controller only:
#WebMvcTest(CraftApisController.class)
Also instead of injecting the real implementation of service, you can use #MockBean so that the real service implementation will be covered by a regular unit test (without spring at all, just plain JUnit/Mockito) and this test could check that your annotations are defined correctly

Spring Data JDBC UnsatisfiedDependencyException

I wanted to move from JdbcTemplate to Spring Data JDBC. However I seem to have some misconfiguration but I cannot figure out where. The errors are "expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}" and "Parameter 0 of constructor ... required a bean ... that could not be found."
I put #Repository on the public repository interfaces extending PagingAndSortingRepository (as I did with the DAO classes extending from JdbcDaoSupport) without success. Then I added #EnableJdbcRepositories with and without package name to the database config class, also no success. I also tried the database config to inherit from AbstractJdbcConfiguration, still the same errors ...
Unfortunately I couldn't find a working example and I now gave up after some trial and error. I still would love to get this working, the version I used is spring-boot-starter-data-jdbc:2.4.0
Code fragments:
DatabaseConfiguration.java
#Configuration
#EnableJdbcRepositories("<basepackage>.repository.jdbc")
#EnableJdbcAuditing(auditorAwareRef = "springSecurityAuditorAware")
#EnableJpaRepositories("<basepackage>.repository")
#EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
#EnableTransactionManagement
#EnableElasticsearchRepositories("<basepackage>.repository.search")
public class DatabaseConfiguration extends AbstractJdbcConfiguration {
}
UserRepository.java
#Repository
public interface UserRepository extends PagingAndSortingRepository<User, String> {
}
QualityResource.java (REST Controller)
public QualityResource(UserRepository userRepository) {
this.userRepository = userRepository;
}
Error messages:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'qualityResource' defined in file [.../backend/build/classes/java/main/.../web/rest/QualityResource.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type '....repository.jdbc.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Application failed to start: Description: Parameter 0 of constructor in <basepackage>.web.rest.QualityResource required a bean of type '<basepackage>.repository.jdbc.UserRepository' that could not be found.

Upgrade from Spring 4 to Spring 5 and run into #Autowired enforcement issue. Any workaround please?

I am upgrading our app from spring 4 to spring 5. The app has a third party dependency library that we can not touch. This library is using spring 4 and one of its beans is autowiring a null bean instance. This works fine in spring 4 but I got the following exception in spring 5:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'LibraryService': Unsatisfied dependency expressed through field '_beanDoesntExist'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'x.y.z.LibraryService$BeanDoesntExist' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
The following is a demo of such a #Autowired usage in the library that would trigger the exception above in spring 5(but worked fine in spring 4):
#Service
public class LibraryService
{
#Autowired
private BeanDoesntExist _beanDoesntExist;
static class BeanDoesntExist {
}
#Configuration
static class Config {
#Bean
public BeanDoesntExist beanDoesntExist() {
return null;
}
}
}
It seems in spring 5, a check is added: https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java#L1293-L1296
if (result instanceof NullBean) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
This check would enforce #Autowired to always get not null instance back if required=true which is the default.
By comparision, in spring 4, there is no such enforcement check and the following code would simply return null in my case: https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java#L1131-L1132
return (instanceCandidate instanceof Class ?
descriptor.resolveCandidate(autowiredBeanName, type, this) : instanceCandidate);
Since I can't touch the library code, I wonder how can I get around the #Autowired enforcement in spring 5? My app can't be started up with this enforcement in place.
It is a breaking change in Spring 5 and has been covered here:
Change in how Spring 5 handles null Beans?
Using Optional or ObjectProvider seems to be a viable workaround.

JAR SPRING #autowired

I create a java archive and i would like integrate this on my application.
on my principal application :
ApplicationContext.xml :
<context:component-scan base-package="com.test.chomage" />
ChomageController.java :
#Controller
#Path("/chomage")
public class ChomageController {
#Autowired
ChomageService chomageService;
#Autowired
OrganisationService organisationService;
}
On my java archive :
appConfig.java
#Configuration
#ComponentScan("com.test.jarPatrimoine")
public class AppConfig {
}
OrganisationService.java
public interface OrganisationService {
//Functions
}
OrganisationServiceImpl.java
#Service
#Transactional
public class OrganisationServiceImpl implements OrganisationService
//Functions
}
When i start my tomcat server, i have the next error :
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'chomageController':
Unsatisfied dependency expressed through field 'organisationService';
nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type
'com.test.jarPatrimoine.service.OrganisationService' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency
annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
Can you help me ?
Thanks
Spring is looking for com.vnf.jarPatrimoine.service.OrganisationService, but scan is configured as #ComponentScan("com.test.jarPatrimoine"). You might have to change it.
Are there any other classes implementing OrganisationService? If so you need to add #Qualifier annotation to inject the right implementation.
#Autowire
#Qualifier("thequalifyingbean")
OrganisationService organisationService;
also use #Controller for your controller class instead of #Component

Spring beans GeoModule bean injection to RepositoryRestMvcConfiguration

I'm currently testing spring data rest, and I want to expose the primary keys (ids) of my entities through the REST interface.
I have found that the proper (?) way to do this is:
public class IdExporterConfiguration extends RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
super.configureRepositoryRestConfiguration(config);
config.exposeIdsFor(User.class);
}
}
The problem is, that if I change my bean definition to this:
<bean class="test.project.util.IdExporterConfiguration"/>
From this:
<bean class="org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration"/>
my application fails to start...
The error is:
Could not autowire field: org.springframework.data.geo.GeoModule org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.geoModule;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.data.geo.GeoModule] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Basically it says that it does not find a GeoModule bean, so it can't autowire it for the RepositoryRestMvcConfiguration base...
Now the fun part is, that is I define the bean:
<bean class="org.springframework.data.geo.GeoModule"/>
The error changes to:
Could not autowire field: org.springframework.data.geo.GeoModule org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.geoModule;
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.data.geo.GeoModule] is defined:
expected single matching bean but found 2: jacksonGeoModule,org.springframework.data.geo.GeoModule#0
So if I don't define a bean, there is 0, but if I define one, there is 2?
I still don't know why, but if I use the #Configuration annotation, then it works...
No GeoModule bean needed, but how can it be, that with the original config as XML bean definition it works, but with the subclass, it does not?

Resources