I'm having an issue using Profiles in Spring, basically, we use them to stub some parts of our microservice, which uses both a connection to DB and a connection to another webservice.
Previously I used one stub profile for both the DB and external webservice :
#Configuration
#EnableAutoConfiguration
#Profile("stub")
#ComponentScan(
basePackages = {"com.consumption.backend"},
excludeFilters = {
#ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.consumption.backend.*.persistence.*"),
#ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.consumption.backend.databackendapi.*")
})
public class StubConfiguration {
#Bean
public DataApi consumptionApi() { return new DataStubApi(); }
#Bean
public RefDayDao refDayDao() { return new RefDayInMemoryDao(); }
#Bean
public RefTypeHourDao refTypeHourDao() { return new RefTypeHourInMemoryDao(); }
}
and this works fine, however I would like to separate this stub into two stubs, one for the DB and one for the external webservices to ensure greater flexibility in our tests.
Stub for the DAO:
#Configuration
#EnableAutoConfiguration
#Profile("stubconfv3")
#ComponentScan(
basePackages = {"com.consumption.backend"},
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.consumption.backend.*.persistence.*")
)
public class StubConfV3Configuration {
#Bean
public RefDayDao refDayDao() { return new RefDayInMemoryDao(); }
#Bean
public RefTypeHourDao refTypeHourDao() { return new RefTypeHourInMemoryDao(); }
}
Stub for the external webservice :
#Configuration
#EnableAutoConfiguration
#Profile("stubdatabackend")
#ComponentScan(
basePackages = {"com.consumption.backend"},
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.consumption.backend.databackendapi.*")
)
public class StubDataBackendConfiguration {
#Bean
public DataApi consumptionApi() { return new DataStubApi(); }
}
The stub for the DAO seems to work fine, however I seem to have an issue with the external webservice API not excluding the implementation properly:
If I launch my application with stub profile everything works fine
If I launch my application with stubconfv3 and stubdatabackend I get an issue with injection, as two classes are found :
Parameter 0 of constructor in
com.consumption.backend.service.DataService required a single bean,
but 2 were found:
dataBackendApi: defined in file [C:\code\consumption-backend\databackend-api\target\classes\com\consumption\backend\databackendapi\DataBackendApi.class]
consumptionApi: defined by method 'consumptionApi' in class path resource
[com/consumption/backend/application/configuration/StubDataBackendConfiguration.class]
Action:
Consider marking one of the beans as #Primary, updating the
consumer to accept multiple beans, or using #Qualifier to identify the
bean that should be consumed
Either the exclusion is not working or there is some tricky thing as it seems to find the .class file in target instead of finding it at runtime in a class loader
Most likely, I do some stupid mistake and am not seeing it ...
EDIT : I noticed if I misstype stubconfv3 profile in StubConfV3Configuration, the injection issue doesn't appear anymore, so I guess you can't combine exclusionFilters in #ComponentScan in two different classes...
The first class will do a componentscan on everything but what she excludes, and the second will do the same, so packages excluded by the second class with still be scanned by the first class.
You could try to annotate your DataBackendApi with
#Profile("!stubdatabackend")
to include it when that profile is not used and exclude it otherwise
Related
The key is that the #RestController class is from a 3rd party package that I can't touch. I want all the other classes in that package but not a particular #RestController because I want to replace those endpoints with my own.
It also seems this class is #Import'ed by another class that's also in the 3rd party package that I don't control (see Edit3 below)
It's similar to this question/answer, however in that question, OP has access to the #RestController and can append a #ConditionalOnExpression, whereas I cannot.
Is there another way to disable a #RestController in spring?
The specific #RestController class I want to disable is GraphQLController.
Edit1
Here are some ways I tried to exclude it but fails
#ConfigurationPropertiesScan("com.mycomp.package")
#SpringBootApplication(exclude = {graphql.kickstart.spring.webflux.GraphQLController.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The above attempt throws a
java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
- graphql.kickstart.spring.webflux.GraphQLController
As suggested below, I also tried #ComponentScan.Filter of ASSIGNABLE_TYPE
#ConfigurationPropertiesScan("com.mycomp.package")
#ComponentScan(excludeFilters={
#ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE, value=graphql.kickstart.spring.webflux.GraphQLController.class)})
#SpringBootApplication
public class Application {
....
}
No errors but it's still instantiating the bean.
Also tried #ComponentScan.Filter of REGEX
#ConfigurationPropertiesScan("com.mycomp.package")
#ComponentScan(excludeFilters={
#ComponentScan.Filter(type= FilterType.REGEX, pattern = "graphql.kickstart.spring.webflux.GraphQLController")})
#SpringBootApplication
public class Application {
....
}
That didn't seem to work either
Also tried #ComponentScan.Filter of CUSTOM
#ConfigurationPropertiesScan("com.mycomp.package")
#ComponentScan(excludeFilters = #ComponentScan.Filter(type = FilterType.CUSTOM,
classes = GraphQLControllerBeanFilter.class))
#SpringBootApplication
public class Application {
....
}
public class GraphQLControllerBeanFilter implements TypeFilter {
#Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) {
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String fullyQualifiedName = classMetadata.getClassName();
return fullyQualifiedName.equals("graphql.kickstart.spring.webflux.GraphQLController");
}
}
The custom filter doesn't seem to ever see the GraphQLController. In fact, I printed out all the fullyQualifiedName it scans and only the ones in my com.mycomp.package package show. Nothing from 3rd party libraries even though it's loading them.
I'm wondering if the #ComponentScan.Filter ever sees 3rd party packages or if maybe I have some other spring setting overriding it.
I also tried adding #Primary to my Controller
#Primary
#RestController
public class MyGraphQLController extends graphql.kickstart.spring.webflux.GraphQLController
But that results in this error
java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'graphql.kickstart.spring.webflux.GraphQLController'
Edit2
This is the 3rd party library that contains the #RestController I'm trying to exclude
implementation "com.graphql-java-kickstart:graphql-kickstart-spring-boot-starter-webflux:11.0.0"
Edit3
It looks like graphql.kickstart.spring.webflux.boot.GraphQLSpringWebfluxAutoConfiguration is importing the GraphQLController in an #Import annotation.
Is there anyway to disable or replace it in this case?
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;
I'm trying to use Spring Boot 2 + Spring Data + Custom Auto Configuration Classes but for some reason can't inject DataSource(provided by HikariCP) in the third class.
#Configuration
#AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
public class InitialAutoConfiguration {
//Beans to load in theory first.
}
#Configuration
#AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
#AutoConfigureAfter(InitialAutoConfiguration.class)
#EntityScan(basePackageClasses = Asset.class)
#EnableJpaRepositories(basePackageClasses = AssetRepository.class,
repositoryBaseClass = BaseRepositoryImpl.class,
repositoryFactoryBeanClass = ExtendedJpaRepositoryFactoryBean.class)
public class JpaAutoConfiguration { //Load Jpa Classes
}
#Configuration
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
#AutoConfigureAfter(JpaAutoConfiguration.class)
#ComponentScan(basePackages = {"com.xxx"})
public class ServiceConfiguration {
#Inject
private DataSource dataSource; //Datasource is null
#Bean
public DbPropertySourcesPlaceholderConfigurer dbPropertySourcesPlaceholderConfigurer() {
DbPropertySourcesPlaceholderConfigurer placeholderConfigurer = new DbPropertySourcesPlaceholderConfigurer(dataSource);
placeholderConfigurer.setPlaceholderPrefix("%{");
placeholderConfigurer.setPlaceholderSuffix("}");
return placeholderConfigurer;
}
}
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xetec.autoconfigure.InitialAutoConfiguration,\
com.xetec.autoconfigure.JpaAutoConfiguration,\
com.xetec.autoconfigure.ServiceConfiguration
Looks like my classes are loading before the Spring Data Classes(DataSourceAutoConfiguration.Hikari).
Is there one way to first load the Spring Boot Starters Classes then after my custom ones please?
Thanks
I am no sure if the following change can solve your issue, but i meet with the similar problem
add static in your method
#Bean
public static DbPropertySourcesPlaceholderConfigurer dbPropertySourcesPlaceholderConfigurer() {
....
}
Your use of highest and lowest precedence is the wrong way round. Your ServiceConfiguration is ordered with highest precedence which means that it will be evaluated first.
Rather than using absolute ordering, I would use #AutoConfigureAfter(DataSourceAutoConfiguration.class)
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.
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.