How to exclude a #Repository from component scan when using Spring Data Rest - spring-boot

in a spring boot project I have problems to exclude some repositories from the component scan.
I have a library that contains some entities and some repositories (JpaRepositories). For some reason I implemented a small Spring Boot Data Rest application that shall be used to give testers a quick access to the entities. Therefore I implemented a repository that extends the PagingAndSortingRepository and is annotated with #RepositoryRestResource.
When the application starts all repository will be scanned and made available. As long as I only want to have the Data Rest repositories available I annotated the componenten scanner to exclude the unwant repositories. But this doesn't work. I checked with the actuator beans endpoint and whatever I do - no repositories are excluded.
To demonstrate the problem I created a simple demo application: https://github.com/magomi/springboot-restdata-repoloading.
To exclude the DataRepository I tried the two approaches:
// exclude V02
#SpringBootApplication
#ComponentScan(excludeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
DataRepository.class})
})
and
// exclude V01
#SpringBootApplication(exclude = { DataRepository.class })
Without success. When I call the /beans endpoint (provided by spring boot actuator) I always see
{
bean: "dataRepository",
aliases: [ ],
scope: "singleton",
type: "org.codefromhell.test.repoloading.DataRepository",
...
},
{
bean: "dataApiRepository",
aliases: [ ],
scope: "singleton",
type: "org.codefromhell.test.repoloading.api.DataApiRepository",
...
},

You can use org.springframework.data.repository.NoRepositoryBean annotation over your repository interface.
From doc:
Annotation to exclude repository interfaces from being picked up and thus in consequence getting an instance being created.
This will typically be used when providing an extended base interface for all repositories in combination with a custom repository base class to implement methods declared in that intermediate interface. In this case you typically derive your concrete repository interfaces from the intermediate one but don't want to create a Spring bean for the intermediate interface.

Because it's a repository and not strictly a #Component, you need to excluded it by adding #EnableJpaRepositories to your application:
#SpringBootApplication
#EnableJpaRepositories(excludeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
DataRepository.class})
})
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}

Related

Spring Boot 2.5 and Spring Data: #NoRepositoryBean unexpected behaviour in multi-module project

I'm facing the following issue in a legacy code that I can't change. I have a multi module project which defines in the commons module a Spring Data interface as below:
package commons;
...
#NoRepositoryBean
public interface MyCustomRepository<P, I extends Number> extends JpaRepository<MyEntity, Integer>
{
MyEntity getOneAndCheck();
}
In another module I extend this interface as follows:
package data;
...
#Repository
public interface MyRepository extends MyCustomRepository<MyEntity, Integer>
{
...
}
So, the idea is that I don't want that Spring Data generates any implementation for the MyEntity getOneAndCheck() method 'cause it is implemented like this:
package data;
...
public class MyCustomRepositoryImpl implements MyCustomRepository
{
...
#Override
public MyEntity getOneAndCheck()
{
...
}
...
}
However, when I'm starting the application, I get the following exception:
...
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract MyEntity commons.MyCustomRepository.getOneAndCheck()! No property getOne found for type MyEntity!
...
So what it seems to happen is that Spring Data tries to generate a Query for the MyEntity getOneAndCheck() method, despite the #NoRepositoryBean annotation. This works as expected in the application I'm gonna migrate from Spring 3 with Spring Data to Spring Boot 2.5.
Not sure if the described behavior has anything to do with the fact that there are multiple Maven modules and that the repositories, the entities and the DTOs are in different modules. Not sure neither if there should be any difference between the way it runs currently with Spring and the one with Spring Boot. But the result is that all of the dozens of repositories in this legacy application are failing with the mentioned exception.
It might be important to mention that the main class needs to use annotations in order to tune the scanning:
#SpringBootApplication(scanBasePackages = "...")
#EnableJpaRepositories(basePackages={"...", "..."})
#EntityScan(basePackages= {"...", "..."})
public class MyApp
{
public static void main(String[] args)
{
SpringApplication.run(MyApp.class, args);
}
}
Not sure whether these annotations are supposed to change anything from the point of view of #NoRepositoryBean but the issue appeared as soon as I added this Spring Boot main class. It worked okay previously without Spring Boot.
Any suggestion please ?
Many thanks in advance.
Kind regards,
Seymour
There are two things that play together:
Spring Data's default custom implementation
Repository fragments
None of these apply because:
The default custom implementation follows the name of the actual repository. In your case, the implementation is named MyCustomRepositoryImpl whereas the repository name is MyRepository. Renaming the implementation to MyRepositoryImpl would address the issue
Since Spring Data 2.0, the repository detection considers interfaces defined at the repository level as fragment candidates where each interface can contribute a fragment implementation. While the implementation name follows the fragment interface name (MyCustomRepository -> MyCustomRepositoryImpl), only interfaces without #NoRepositoryBean are considered.
You have three options:
extracting your custom method into its own fragment interface and providing an implementation class that follows the fragment name:
interface MyCustomFragement {
MyEntity getOneAndCheck();
}
class MyCustomFragementImpl implements MyCustomFragement {
public MyEntity getOneAndCheck() {…}
}
public interface MyRepository extends MyCustomRepository<MyEntity, Integer>, MyCustomFragment {…}
Set the repositoryBaseClass via #EnableJpaRepositories(repositoryBaseClass = …) to a class that implements the custom method.
If you cannot change the existing code, you could implement a BeanPostProcessor to inspect and update the bean definition for the JpaRepositoryFactoryBean by updating repositoryFragments and adding the implementation yourself. This path is rather complex and requires the use of reflection since bean factory internals aren't exposed.

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;

Consider defining a bean for jpa repository

My project structure is as follows:
java/com.company.foo/container/configuration/
This folder contains
#ComponentScan({"com.company.foo.module.project",
"com.company.foo.module.user"})
#Configuration
#EnableScheduling
#Import(value = {
SecurityConfiguration.class})
public class ApplicationContextConfiguration {
}
My ResourcePlannerApplication is in this folder:
java/com.company.foo/container/
and has following annotations:
#Import({ApplicationContextConfiguration.class})
#SpringBootApplication
Now I have two modules project and user with both the same structure:
java/com.company.foo/module/user/dao/
This folder contains:
public interface UserRepository extends JpaRepository<UserEntity, Long> {
UserEntity findByUsername(String username);
}
now when I start the app it tells me:
Consider defining a bean of type 'com.company.foo.module.user.dao.UserRepository' in your configuration.
I'm not seeing the problem because the ComponentScan is scanning all the folders?
JPA repositories are not picked up by component scans since they are just interfaces whos concrete classes are created dynamically as beans by Spring Data provided you have included the #EnableJpaRepositories annotation in your configuration:
#ComponentScan({"com.company.foo.module.project",
"com.company.foo.module.user"})
#Configuration
#EnableScheduling
#EnableJpaRepositories("com.company.foo.module.user")
#Import(value = {
SecurityConfiguration.class})
public class ApplicationContextConfiguration {
}
Plog's answer is correct.
I just want to add that, similar solution is applicable for Mongo Repositories as well (where we have interface as a repository).
Suppose, repository package is:
package com.example.respository;
Enable mongo repositories in spring application code, like below:
#EnableMongoRepositories("com.example.repsoitory")

How to exclude/disable a specific auto-configuration in Spring boot 1.4.0 for #DataJpaTest?

I am using the #DataJpaTest from Spring for my test which will then use H2 as in memory database as described here . I'm also using Flyway for production. However once the test starts FLyway kicks in and reads the SQL file. How can I exclude the FlywayAutoConfiguration and keep the rest as described here in spring documentation in order to let Hibernate create the tables in H2 for me?
#RunWith(SpringRunner.class)
#DataJpaTest
public class MyRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private MyRepository triggerRepository;
}
Have you tried the #OverrideAutoConfiguration annotation?
It says it "can be used to override #EnableAutoConfiguration".
I'm assuming that from there you can somehow exclude FlywayAutoConfiguration
like so:
#EnableAutoConfiguration(exclude=FlywayAutoConfiguration.class)
Adding the dependency on an in-memory database to my build.gradle
e.g. testRuntime "com.h2database:h2:1.4.194"
And adding flyway.enabled=false to application.properties in src/test/resources worked for me.
I am converting an old JDBC app into a spring-data-jpa app and I'm working on the first tests now. I kept seeing a security module instantiation error from spring-boot as it tried to bootstrap the security setup, even though #DataJpaTest should theoretically be excluding it.
My problem with the security module probably stems from the pre-existing implementation which I inherited using PropertySourcesPlaceholderConfigurer (via my PropertySpringConfig import below)
Following the docs here:
http://docs.spring.io/spring-boot/docs/1.4.x/reference/htmlsingle/#test-auto-configuration
and your comments on #LiviaMorunianu's answer, I managed to work my way past every spring-boot exception and get JUnit to run with an auto-configured embedded DB.
My main/production spring-boot bootstrap class bootstraps everything including the stuff I want to exclude from my tests. So instead of using #DataJpaTest, I copied much of what it is doing, using #Import to bring in the centralized configurations that every test / live setup will use.
I also had issues because of the package structure I use, since initially I was running the test which was based in com.mycompany.repositories and it didn't find the entities in com.mycompany.entities.
Below are the relevant classes.
JUnit Test
#RunWith(SpringRunner.class)
#Transactional
#Import({TestConfiguration.class, LiveConfiguration.class})
public class ForecastRepositoryTests {
#Autowired
ForecastRepository repository;
Forecast forecast;
#Before
public void setUp() {
forecast = createDummyForecast(TEST_NAME, 12345L);
}
#Test
public void testFindSavedForecastById() {
forecast = repository.save(forecast);
assertThat(repository.findOne(forecast.getId()), is(forecast));
}
Live Configuration
#Configuration
#EnableJpaRepositories(basePackages = {"com.mycompany.repository"})
#EntityScan(basePackages = {"com.mycompany.entity"})
#Import({PropertySpringConfig.class})
public class LiveConfiguration {}
Test Configuration
#OverrideAutoConfiguration(enabled = false)
#ImportAutoConfiguration(value = {
CacheAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
TransactionAutoConfiguration.class,
TestDatabaseAutoConfiguration.class,
TestEntityManagerAutoConfiguration.class })
public class TestConfiguration {
// lots of bean definitions...
}
PropertySpringConfig
#Configuration
public class PropertySpringConfig {
#Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
throws IOException {
return new CorePropertySourcesPlaceholderConfigurer(
System.getProperties());
}
}
In my particular case, i needed to disable the FlywayDB on in-memory integration tests. These are using a set of spring annotations for auto-configuring a limited applicationContext.
#ImportAutoConfiguration(value = TestConfig.class, exclude = FlywayAutoConfiguration.class)
the exclude could effectively further limit the set of beans initiated for this test
I had the same problem with my DbUnit tests defined in Spock test classes. In my case I was able to disable the Flyway migration and managed to initialize the H2 test database tables like this:
#SpringBootTest(classes = MyApplication.class, webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = ["flyway.enabled=false", "spring.datasource.schema=db/migration/h2/V1__init.sql"])
I added this annotation to my Spock test specification class. Also, I was only able to make it work if I also added the context configuration annotation:
#ContextConfiguration(classes = MyApplication.class)
I resolved the same issue by excluding the autoconfiguration from my application definition, i.e.
#SpringBootApplication(exclude = {FlywayAutoConfiguration.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
you can also sue the following annotation:
#RunWith(SpringRunner.class)
#DataJpaTest(excludeAutoConfiguration = {MySqlConfiguration.class, ...})
public class TheClassYouAreUnitTesting {
}
You can just disable it in your test yaml file:
flyway.enabled: false

Conditional ComponentScan on package

In a Spring Boot Application I have a package with Application class like
#SpringBootApplication
class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application);
application.run(args);
}
}
which automatically has ComponentScan set up from that class's package by default. Then I have several subpackages each containing several component and service beans (using annotations). But for the purpose of reuse of this application for different use cases, I need to enable/disable all the components in some of the subpackages, preferably by a property.
That is I have subpackages like
org.example.app.provider.provider1
org.example.app.provider.provider2
Now, based on some property I would like to enable (scan for) beans in one of the packages, e.g.
provider1.enabled=true
I thought I could make ConditionalOnProperty on Configuration class work like that, but the problem is, that the beans are picked up by the default #SpringBootApplication component scan (i.e. the subpackage Configuration class does not override the top level one)
So I thought I would exclude the packages, but this adds more work (and knowledge) when a new provider package is needed (need to know in advance to add an explicit exclude for that package).
Is there any other way how to do this I can't figure out?
Load the provider components conditionally
A Spring Configuration annotated with a #ConditionalOnProperty would do just that:
#Configuration
#ComponentScan("org.example.app.provider.provider1")
#ConditionalOnProperty(name = "provider1.enabled", havingValue = "true")
public class Provider1Configuration {
}
#Configuration
#ComponentScan("org.example.app.provider.provider2")
#ConditionalOnProperty(name = "provider2.enabled", havingValue = "true")
public class Provider2Configuration {
}
Then exclude the components under org.example.app.provider.*
Now, all you need is to exclude the providers from Spring Boot Application (and let the CondtionalOnProperty do its work). You can either:
(1) move the provider packages so that they are not below the Spring Boot Application
For example, if the Spring Boot main is in org.example.app, keep the #Configuration in org.example.app but the providers in org.example.providers
(2) Or exclude the provider package from Spring Boot (assuming the #Configuration are in org.example.app for example):
SpringBootMain.java:
#SpringBootApplication
#ComponentScan(excludeFilters = #ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "org.example.app.provider.*"))
class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application);
application.run(args);
}
}

Resources