The Actual Different: Spring boot #ComponentScan vs #Import when involving ConditionalOnClass - spring

Context
I know there are a lot of posts regarding the comparison between these two. All of them just focus on #Import works on single Config file while #ComponenentScan will scan all config, bean, service files.
I find something new, hope someone can give me a good explanation behind this:
Prerequisite Artifact:
org.springframework.boot.autoconfigure
org.springframework.boot.test
#SpringBootTest(classes = test1char.class)
#ComponentScan("org.springframework.boot.autoconfigure.jackson")
//#SpringBootTest(classes = test1char.class)
//#Import(org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.class)
public class ObjectMapperTest {
//cannot autowired: no such bean when using ComponenentScan
// Can autowired when either using #Import
// or #SpringBootTest(classes = org.springframework.boot.autoconfigure.jackson.class)
#Autowired
ObjectMapper objectMapper;
#Test
void main() {
var date = LocalDateTime.now();//LocalDateTime
String s;
try {
s = objectMapper.writeValueAsString(date);
System.out.println(s);
}catch (JsonProcessingException e){
System.out.println(e);
}
}
}
ObjectMapper bean producer class:
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.class for reference
#AutoConfiguration
#ConditionalOnClass({ObjectMapper.class})
public class JacksonAutoConfiguration {
.... omit
#Configuration(
proxyBeanMethods = false
)
#ConditionalOnClass({Jackson2ObjectMapperBuilder.class})
static class JacksonObjectMapperConfiguration {
JacksonObjectMapperConfiguration() {
}
#Bean
#Primary
#ConditionalOnMissingBean
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}
Issue
Basically the ObjectMapper cannot autowired: no such bean when using ComponenentScan. But it can be autowired when either using #Import or #SpringBootTest(classes = org.springframework.boot.autoconfigure.jackson.class)
Note: test1char.class is just a dummy class so that it has a context while not using the default SpringBootApplication which has everything in class path
I know it must have something to do with ConditionalOnClass which requires some class in jar org.springframework.boot.autoconfigure.jackson to be in classpath. So I boldly guess only #Import and #SprngBoot and #SpringBootTest can load class or package to be in classpath?
That's why quite often ObjectMapper cannot be autoconfigured in #SpringBootTest annotated integration test as we often forgot to add the jar to it.
Update
Hate to say with #EnableAutoConfiguration it works all good like below:
#ComponentScan("org.springframework.boot.autoconfigure.jackson")
#EnableAutoConfiguration
Update
Actually #EnableAutoConfiguration alone also works without #ComponentScan.
-- One who is always confused by SpringBoot config xD
Attachment: Condition Report output in Intellij when using #ComponenetScan
============================
CONDITIONS EVALUATION REPORT
Positive matches:
None
Negative matches:
None
Exclusions:
None
Unconditional classes:
None

Related

Overriding Springboot Beans in integration test

I know there are a lot of examples out there about overriding beans in integration tests, but I'm getting a bit confused with Springboot versions, and I don't know what am I doing wrong.
I'm basically running a #SpringBootTest with #ActiveProfiles("test") and RestAssured. All annotations are in a base class I called BaseIntegrationTest, which looks like the following:
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(
initializers = [BaseIntegrationTest.Companion.Initializer::class],
classes = [BaseIntegrationTest.Companion.ClockConfiguration::class]
)
abstract class BaseIntegrationTest {
#LocalServerPort
var localPort: Int = 0
#BeforeEach
fun beforeEachTest() {
RestAssured.port = localPort
}
companion object {
#TestConfiguration
class ClockConfiguration {
#Bean
fun mockClock(): Clock {
return Clock.fixed(
LocalDate
.of(2022, 1, 1)
.atTime(0, 0)
.toInstant(ZoneOffset.UTC),
ZoneId.of("UTC")
)
}
#Bean
fun mockCountryService(): CountryService {
return MockCountryService()
}
}
class MockCountryService : CountryService() {
override fun findByCode(countryCode: String): Country? {
return null
}
}
}
}
As you can see, I have a nested class with #TestConfiguration annotation (as I read it automatically recogizes it). By the way, I also included this CountryService bean because I was initially trying to mock the Clock, without success. I used this service to make sure that it was not a clock issue, but a bean overriding issue.
But when I run the tests, and put breakpoints along the production code, I see that what gets instantiated are the real beans, not these ones. I also tried to use #Configuration instead of #TestConfiguration, and adding #Primary to the beans here, but no luck.
If I remove the original beans, and I leave only these ones, then when debugging the production code I actually get the mocked beans, so I guess it has something to do in overriding them.
Springboot version is 2.7.2
Can anyone help me with that?
Thanks!

Not able to prevent couchbase autoconfiguration during tests

I am trying to prevent the application from attempting to connect to the DB while running the Unit tests. Following is what I have done.
#SpringBootApplication(exclude = {
CouchbaseDataAutoConfiguration.class,
CouchbaseAutoConfiguration.class,
})
#ComponentScan(excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
ApplicationStartup.class, MessageApplication.class }))
public class MessageApplicationTests {
public static void main(String[] args) {
SpringApplication.run(MessageApplicationTests.class, args);
}
}
#ActiveProfiles("test")
#SpringBootTest(classes = MessageApplicationTests.class)
class TestClass {
#Autowired
Serviceclass serviceclass;
#Test
void testMethod() {
}
}
Apart from this, I have added the following in application-test.yml
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration
- org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration
- org.springframework.cloud.aws.autoconfigure.messaging.MessagingAutoConfiguration
Both are not helping.
Can someone help me understand what is wrong here?
Also exclude your Config class (the one that extends AbstractCouchbaseConfig)
But if you have any references to repositories such as via Autowire or as args to #Service constructors, the application will fail to start. The exclude of auto configuration classes did not seem to matter when I tried it.
#ComponentScan(excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
ApplicationStartup.class, MessageApplication.class, Config.class}))
Probably not related to your issue, but I found that with multiple #SpringBootApplication classes (you have MessageApplication and MessageApplicationTests, right?), that Spring goes through the auto-config classes for both of them, not just the one in
SpringApplication.run(MessageApplicationTests.class, args) ) So one would need #SpringBootApplication excludes on both classes to completely exclude them (although I found that excluding didn't change anything).
The spring-data-couchbase project tests provide a mock couchbase server (src/test/resources/integration.properties -> mocked) or can use a standalone couchbase server (unmanaged). That might be useful for your testing.
The above answer posted by Michael Reiche is correct. Adding few more points to address the concern raised by him.
We need to exclude the configuration class for Couchbase. But the autowired repository beans would create a problem then.
To resolve it we can mock the repository beans so that it doesn't try to create actual repository beans and load them to the context.
Not including the autoconfiguration classes in the exclusion list did matter for me, as it would try to configure the Couchbase since the dependency is there in the classpath
#SpringBootApplication(exclude = {
CouchbaseDataAutoConfiguration.class, CouchbaseAutoConfiguration.class,
CouchbaseRepositoriesAutoConfiguration.class, CouchbaseReactiveDataAutoConfiguration.class,
CouchbaseReactiveHealthContributorAutoConfiguration.class
})
#ComponentScan(excludeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {
ApplicationStartup.class, MessageApplication.class , CouchBaseConfiguration.class }))
public class MessageApplicationTests {
#MockBean
Repositoryclass repoBean;

IntelliJ Idea + Could not autowire. No beans of type found

I keep seeing below error in my IntelliJ Idea, however the code works fine during execution.
Could not autowire. No beans of 'PortfolioRequestHandler' type found. less... (Ctrl+F1)
Inspection info:Checks autowiring problems in a bean class.
Sample Code
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#SpringBootTest(classes = {Application.class})
public class PortfolioRequestHandlerTest {
#Autowired
private PortfolioRequestHandler portfolioRequestHandler;
...
...
}
How do I get rid of this? I am using IntelliJ Idea ULTIMATE 2018.2
Are you sure that your Spring beans are wired correctly and that it's an IDE problem?
check if your PortfolioRequestHandler class is annotated with #Service, #Component or #Repository (bean config via component scanning)
otherwise check if your bean is wired in a #Configuration annotated class -> in that case there should be a method that returns an instance of type PortfolioRequestHandler and that's annotated with #Bean
try adding a configuration class (as mentioned in 2.) and add this class to your #SpringBootTest(classes = {...} annotation; see example below
#Configuration
public class CustomBeanConfig {
#Bean
public PortfolioRequestHandler get PortfolioRequestHandler() {
return new PortfolioRequestHandler();
}
}
#SpringBootTest(classes = {Application.class, CustomBeanConfig.class})
have a look at this one, maybe helps: https://stackoverflow.com/a/50267869/150623

Spring conditional component scan configuration

I have a configuration class which registers beans based on a very simple condition (checking a property value in application.properties). The configuration class and the condition are the following:
#Configuration
#Conditional(DatabaseConfigurationCondition.class)
#ComponentScan(basePackageClasses = DBConfigComponents.class)
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
}
and
public class DatabaseConfigurationCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return conditionContext.getEnvironment().getProperty("configuration.type").contains("db");
}
}
In addition of the beans registered in this configuration class I have component scan which scans for other components. When the condition is not met, I expect the beans which are defined in the configuration class not to be registered (which happens to be a case), but also I expect other classes which are annotated with #Component (or #Repository, #Service, etc.. ) and are in same folder as DBConfigComponents.class marker interface not to be registered, which does not happen. Beans which are scanned are always registered, no matter if the condition is fulfilled or not.
When I put the #Conditional(DatabaseConfigurationCondition.class) on each #Component annotated class, than it's working correctly, but I don't want to put it on each class separately.
Any suggestion?
Fortunately, I managed to fix this. The problem in my case was that I had another #ComponentScan annotation placed in other configuration class in other Maven module - not conditional on any property. The components which are in same package as DBConfigComponents marker interface were actually scanned by the other configuration class.
The way #ComponentScan works is on package level. Although, in different Maven modules, both configuration classes were in same package. #ComponentScan works perfectly fine with #Conditional. No need #Conditional to be placed on each component separately.
The best way to achieve this is not to annotate these beans using #Component / #Service and #Repository annotations. Instead you should return these as part of the configuration you have setup which would be DatabaseConfigurationLoader. See sample below.
#Configuration
#Conditional(DatabaseConfigurationCondition.class)
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
#Bean
public SomeService someService() {
return new SomeService();
}
#Bean
public SomeComponent someComponent() {
return new SomeComponent();
}
}
Note: Typically #Configuration with #Conditional are used in libraries that you want to include in your spring boot application. Such libraries should not share the same package as your spring boot application. Thus they should not be picked up by #ComponentScan annotation. Beans from libraries should not be annotated with #Component / #Service / #Repository annotations. Spring suggests using AutoConfiguration for that. See https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html & https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html
No need to implement Condition interface, you need to use '#ConditionalOnProperty' annotation:
#Configuration
#ComponentScan(basePackageClasses = DBConfigComponents.class)
#ConditionalOnProperty(name = "configuration.type", havingValue = "db")
public class DatabaseConfigurationLoader {
#Bean
public DatabaseConfigurationRepository databaseConfigurationRepository() {
return new DatabaseConfigurationRepository();
}
}
you can use 'prefix' instead of 'havingValue' depending on your needs.

Spring #Configuration bean created in #Bean method not enhanced by CGLIB

I'm trying to create a MainConfig that imports another Config by using an #Bean method instead of #Import like this :
#Configuration
public class MainConfig {
#Bean
public Service service() {
return new Service(infrastructureConfig().database());
}
#Bean
public OtherService otherService() {
return new OtherService(infrastructureConfig().database());
}
#Bean
public InfrastructureConfig intrastructureConfig() {
return new InfrastructureConfig();
}
}
#Configuration
public class InfrastructureConfig {
#Bean
public Database database() {
return new Database();
}
...
}
When using this technique, the Database is created twice because Spring doesn't seem to consider the #Configuration annotation on InfrastructureConfig. When using #Import, it works fine.
I don't want to use #Import because I want to mock my InfrastructureConfig like this :
#Configuration
public class TestConfig extends MainConfig {
#Override
public InfrastructureConfig infrastructureConfig() {
return mock(InfrastructureConfig.class);
}
}
Am I missing something or it is not supported ?
Thanks
When I first tried out Spring Java configuration I think I made the same assumption and was surprised when it didn't work.
I'm not sure this is the neatest way of solving this but I have used the following approach successfully.
To include that #Configuration class you can add this annotation to your MainConfig:
#ComponentScan(basePackages = "org.foo", includeFilters = {#Filter(filterType = ANNOTATION, value = CONFIGURATION)}, excludeFilters = {#Filter(filterType = ASSIGNABLE_TYPE, value = MainConfig)})
Since #Configuration classes are also candidates for component scanning this allows you to scan for all classes annotated with #Configuration. Since you're putting this annotation on MainConfig you need to exclude that with the ASSIGNABLE_TYPE filter since you'll get a circular reference.
I opened a Spring ticket SpringSource JIRA and they said that it is a known limitation and it is working as designed.

Resources