4

I'm making some tests using custom starters for spring boot. I managed to configure everything except the entities. I've tryed using @Import to load entities in the @AutoConfiguration class but this does not work. Instead if we use @EntityScan in the starter the entities are loaded, but if you import this starter and have entities in the project that depends on the starter you are forced to use @EntityScan also in it, and in my opinion this breaks the autoconfiguration meaning of the starter because when you import a starter you should do nothing in order to use it, yes you can override the default configuration but not forced to do anything maybe to declare some properties.

Example of autoconfiguration class in the starter:

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = "com.example.springbootstarterexample.repository")
@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {

}

and then if you have entities in the consumer of the starter you have to do this if you have entities in it:

@SpringBootApplication
@EntityScan(basePackages = "com.example.springbootconsumer.model")
public class SpringBootConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootConsumerApplication.class, args);
    }

}

Otherwise we can remove @EntityScan from the starter and do this in the consumer:

@SpringBootApplication
@EntityScan(basePackages = {"com.example.springbootconsumer.model", "com.example.springbootstarterexample.domain"})
public class SpringBootConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootConsumerApplication.class, args);
    }

}

but this totaly brakes the autoconfiguration, because you have to know where the entities are in the starter in order to start the application. I've write an example if interested.

EDIT tryed with @AutoConfigurationPackage

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
//@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@AutoConfigurationPackage(basePackageClasses = {SomeEntity.class, SomeEntityRepository.class})
@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
//@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {

}

In this way the repository is not istantiated

Description:

Parameter 0 of constructor in com.example.springbootstarterexample.service.SomeServiceImpl required a bean of type 'com.example.springbootstarterexample.repository.SomeEntityRepository' that could not be found.


Action:

Consider defining a bean of type 'com.example.springbootstarterexample.repository.SomeEntityRepository' in your configuration.

If I use @EnableJpaRepositories the repository is find for injection but not the entity

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@AutoConfigurationPackage(basePackageClasses = {SomeEntity.class})
@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
//@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {

}

Error:

Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.example.springbootstarterexample.domain.SomeEntity

Using the name of the package I have the same result

EDIT 2 The @AutoConfiguration class is loaded by META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports removed @Import:

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
//@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@AutoConfigurationPackage(basePackageClasses = {SomeEntity.class, SomeServiceImpl.class, SomeEntityController.class, SomeEntityRepository.class})
//@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
//@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {

}

trying to inject something in the consumer:

Description:

Parameter 0 of constructor in com.example.springbootconsumer.SpringBootConsumerApplication required a bean of type 'com.example.springbootstarterexample.service.SomeService' that could not be found.


Action:

Consider defining a bean of type 'com.example.springbootstarterexample.service.SomeService' in your configuration.

This seems to not load any configuration at all.

EDIT 3 put the log level to TRACE and put all classes under the same package, the package of ExampleAutoConfiguration class that now looks like this:

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
//@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@AutoConfigurationPackage
//@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
//@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {

}

I found log of the @AutoConfiguration class being scanned but I can't find any bean defined in the package in the logs:

2022-09-08 20:03:24.495 TRACE 17132 --- [           main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'com.example.springbootstarterexample.autoconfigure.ExampleAutoConfiguration'

if I use normal configuration i see all beans been created

2022-09-08 22:31:34.580 TRACE 2308 --- [           main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'com.example.springbootstarterexample.service.SomeServiceImpl'
2022-09-08 22:31:34.581 TRACE 2308 --- [           main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'com.example.springbootstarterexample.controller.SomeEntityController'
2022-09-08 22:31:34.585 TRACE 2308 --- [           main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'com.example.springbootstarterexample.autoconfigure.ExampleAutoConfiguration'
2022-09-08 22:31:34.685 TRACE 2308 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Spring Data JPA - Registering repository: someEntityRepository - Interface: com.example.springbootstarterexample.repository.SomeEntityRepository - Factory: org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
2022-09-08 22:31:39.094 DEBUG 2308 --- [           main] org.hibernate.cfg.Ejb3Column             : Binding column: Ejb3DiscriminatorColumn{logicalColumnName'DTYPE', discriminatorTypeName='string'}
2022-09-08 22:31:39.112 DEBUG 2308 --- [           main] o.h.cfg.annotations.EntityBinder         : Import with entity name SomeEntity
2022-09-08 22:31:39.113 TRACE 2308 --- [           main] o.h.b.i.InFlightMetadataCollectorImpl    : Import: SomeEntity -> com.example.springbootstarterexample.domain.SomeEntity
2022-09-08 22:31:39.114 TRACE 2308 --- [           main] o.h.b.i.InFlightMetadataCollectorImpl    : Import: com.example.springbootstarterexample.domain.SomeEntity -> com.example.springbootstarterexample.domain.SomeEntity
11
  • There is a, bit hidden, annotation for this. Use [@AutoConfigurationPackage] (docs.spring.io/spring-boot/docs/current/api/org/springframework/…) and ditch the @EnableJpaRepositories and @EntityScan. You might even need to ditch the @AutoConfigureAfter. Commented Aug 30, 2022 at 14:20
  • @M.Deinum tryed various configurtion with @AutoConfigurationPackage nothing worked, if you can make an example I'will be happy to make a test Commented Sep 2, 2022 at 14:54
  • What have you tried so far? What combinations? It should be a matter of putting that annotation on your auto config. Commented Sep 2, 2022 at 18:59
  • @M.Deinum updated the question with more infos Commented Sep 5, 2022 at 18:01
  • You need to remove the @ComponentScan as well and make sure you properly registered your auto configuration class in the spring.factories or org.springframework.boot.autoconfigure.AutoConfiguration.imports file, depending on your spring boot version. See docs.spring.io/spring-boot/docs/current/reference/html/… Commented Sep 6, 2022 at 4:57

3 Answers 3

6

Finally found a solution thanks to this post. The configuration class becomes:

@AutoConfigureBefore(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@Import({SomeServiceImpl.class, SomeEntityController.class, StarterEntityRegistrar.class /*, SomeEntity.class NOT WORKING*/})
public class ExampleAutoConfiguration {

}

and the registar for entities:

public class StarterEntityRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, SomeEntity.class.getPackageName());
    }

}

a working example

you can use the register method to add all packages that you need:

Programmatically registers the auto-configuration package names. Subsequentinvocations will add the given package names to those that have already beenregistered. You can use this method to manually define the base packages that willbe used for a given BeanDefinitionRegistry. Generally it's recommended thatyou don't call this method directly, but instead rely on the default conventionwhere the package name is set from your @EnableAutoConfigurationconfiguration class or classes.

Edit:

upgraded example to spring boot 3.2.1 still working

Sign up to request clarification or add additional context in comments.

3 Comments

you wouldn't happen to have this working with spring boot 3.x would you? Asking because I've created custom starter/autoconfiguration and I from what I can tell the Registrar approach to making entities known does not work anymore.
@Micke I've not tryed with spring boot 3.x, I will make some trial I will let you know
@Micke I've upgraded the test project to spring boot 3.2.1, everything works fine
0

You need to add @ComponentScan to configuration bean.

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = "com.example.springbootstarterexample.repository")
@ComponentScan("com.example.springbootstarterexample")
@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {

}

7 Comments

it's a bad practice to use @ComponentScan in the starter docs. And this not solves the problems related to @EntityScan
No, you are post link to @Import configuration beans, I mean you should use @ComponentScan to load your services annotated as @Service or @Component @ComponentScan("com.example.springbootstarterexample.controller")
Nope the doc stays to use @Import not @ComponentScan also for beans annotated not only for configuration classes
You do realize that the first thing @SpringBootApplication does is @ComponentScan all classes in that package and below, right? Ideally you would have a configuration file in the right package with @ComponentScan, and you would @Import that configuration class in your application.
Also, if your entities are in a different jar, you can expose them explicitly to Spring Boot in file META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, one class per line
|
0

An interesting problem, but if you had an agreed way with consumers of your library on how to share information where their entities are, like for example they would provide a META-INF/entityscanforyouapp file with a list of packages where they have their entities

Consumer 1 would create META-INF/entityscanforyouapp

com.consumerapp1.entities

Consumer 1 would create META-INF/entityscanforyouapp

com.consumerapp2.entities

You could discover all these files and then just create an EntityManagerFactory bean that would include all the discovered packages

@Bean
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder, DataSource dataSource) {
    Map<String, Object> vendorProperties = <... read the additional hibernate properties if you need...>;

    String[] packagesWithEntities = discoverAllThePackages();

    return factoryBuilder.dataSource(datasource).packages(packagesWithEntities).properties(vendorProperties).build();
}

This is kind of "rough and ready" but should provide a good starting point if you want to go that way.

2 Comments

Still you have to do work in the consumer and goes against the starter "philosophy", I have to take a look to spring-boot-starter-batch because the spring batch needs some tables to work, but I don't know what they are using
🤷‍♂️ spring-boot-starter-batch uses JdbcTemplate, not entities. So it doesn't use JPA. It doesn't really go against the starter philosophy at all. For example if you're using the jpa starter you can use @EnableJpaRepositories to tell where your entities are. But anyway you can do a full class path scan for entities to discover @Entity annotated classes and use the above pattern to register them all. Will just take a long time to start up :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.