3

Our project has a library that defines several beans. Historically, it's a simple artifact, no autoconfig enabled and all other applications are using this artifact together with @ComponentScan.

Now, I realize, that it should rather be an autoconfiguration dependency, with @ConditionalOnMissingBean etc., to provide more flexibility for the application.

According to the docs, autoconfigurations should not be a candidate for component scanning. As far as I understand, it is only critical for situations when your configuration is @ConditionalOn something, so it does not get scanned twice.

What would be the correct way to perform the transition from library + @ComponentScan to autoconfiguration, assuming all the consumers of the same library cannot be updated instanly?

Will it cause any major issues if the autoconfig get scanned? Or can it be limited to cases with the usage of @ConditionalOn etc?

I know it should be possible to create another maven artifact, make it dependant on the first one and define autoconfiguration, but I would like to avoid creating another now.

Thanks, Pavlo

  • What is the problem? Release the new library as starter on nexus with a new version of course and then start removing `@ComponentScan` from the consumers and update the version. – Pp88 Aug 23 '22 at 19:27
  • There are several libs like that and they share the common package root, so most of the consumers use the common part on the component scan. So if new version is released - every consumer will have to be updated. I know the setup sounds horrible, but it’s a big project and not everything in my hands. – Pavlo Zasiadko Aug 23 '22 at 20:10
  • Do you have N libraries with the same package defining beans in them? So you can use one package in the `@ComponentScan`? This is a hell. You should refactor all the package of the libriaries if I’ve not misunderstood – Pp88 Aug 23 '22 at 20:16
  • Libs and packages go like ‘com.acme.lib1’, ‘com.acme.lib2’ and applications use ‘@ComponentScan(“com.acme”) to avoid listing all. Totally agree on refactoring, hence the question. – Pavlo Zasiadko Aug 23 '22 at 20:39
  • This is not so bad as I was thinking. Every library as it’s own package. To go faster just use in each library a `@Configuration` (that you must use because it my must be linked in spring.factories) with `@ComponentScan(basePackages=com.acme.libn)`. It’s a different story if you want to start to use `@ConditionalOnMissingBean` or something like that. In this case you have to start defining and constructing the beans in the configuration class. But no need to refactor the pakages in the consumers. I think those are the roads – Pp88 Aug 23 '22 at 20:50
  • Of course you have to finish the refactor on each library before starting refactoring the consumers for the problem of @ComponentScan with the parent package. Once you have finished with the libraries you can start to deploy one consumer at time. The other option e will be to change the com.acme package in the libriaries to avoid the component scan – Pp88 Aug 23 '22 at 21:02

1 Answers1

1

So I have this solution in my mind:

  1. Start simple in each starter library start defining one @Configuration class with a @ComponentScan(basePackages=“com.acme.libn) when done release all the libriaries on the nexus (Note that this is a bad practice the starters should define the beans in the @Configuration classes but for this step will be used as a workaround).
  2. Go on the consumers and remove @ComponentScan, update library and deploy. This will be the fastest way to remove the scanning of the beans on the main com.acme package.
  3. Now you are free to work on each library as you prefer. Example: refactor the packages, declaring beans in the @Configuration, use @ConditionalOn…, remove @ComponentScan, use more @Configuration classes (you have to link them in the spring.factories file), start using prefix for using propeties defined in the consumers …, you can work in parallel on this task with your team. And release one library when it’s ready if you don’t want to release all of them together.
  4. Re go in the consumers and refactor if needed, update the library that are ready and deploy. Go to step 3 till the libriaries are finished.

Otherwise you have to refactor com.acme package in each library and in each consumers, keeping the @ComponentScan in the consumers. When done with all the libriaries you can then remove it. You don’t have to do it all together, you can do it one library at time and one consumer at time.

PS: if you have entities and you are using spring-data in your libraries let me know I will update the answer because there is extra work to do.

I’ve read the documentation you have linked and I don’t know if it’s outdated or not. I’m using spring.factories file as for example stated here.I’m using spring boot 2.7.0. So check the correct configuration for the version of spring boot you are using

EDIT I've read better the documentation and digged into the spring boot code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.import can be used to tell spring where the @Configuration\@AutoCOnfiguration classes are. You can also use META-INF\spring.factories to declare the @Configuration\@AutoConfiguration classes. In the source code they are using the first option. Honestly I've not undersood the difference.

I think I've also discovered how to expose @RestController or any other component in a starter without using @ComponentScan as they say in the doc you must use @Import example:

@Configuration
@Import({RestController.class, SomeService.class})
class ConfigurationClass {
}
@RestController
class RestController {
}
@Service
class SomeService {
}

This will make the SomeService avaiable for injection, and I think this will expose automatically the endpoint defined in RestController, like spring-boot-starter-actuator.

For the repositories of spring-data-jpa you have to use this in the ConfigurationClass example:

@Configuration
@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = "com.example")
@Import({Entity1.class, Entity2.class})
class ConfigurationClass {
}
@Entity
public class Entity1 {
}
@Entity
public class Entity2 {
}

In this way the repositories interfaces present in com.example will be ready for injection in the configuration class or outside the jar. In my project I was able to load entities only using @EntityScan in the consumer of the starter I have to make a try with @Import.

All the things that I've writed in the EDIT section must be tested, the only exception is the configuration for jpa repositories with @EntityScan on the consumer. I've had no time for test right now.

So in the end you can remove then @ComponentScan in the first step I've writed and use @Import, but you have to list all the classes that are annotated like @Component\@Service.... In this way you don't have to declare and construct all the beans of the step 3 in the configuration classes.

EDIT 2 I've made some test you will find the code here what i discovered:

  1. Adding the configuration classes in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.import doesn't work, you have to use META-INF\spring.factories if you check the code I've commented the configuration class in the file
  2. @Import with entities doesn't work, unlikly you have to put @EntityScan in the cosnumer declaring all packages where the entities are.

For the rest everithing is fine. You can play with it:

POST localhost:8080/someEntities
request body
{
    "name" : "entity1"        
}
response
{
    "id": 1,
    "name": "entity1"
}
{
    "id": 1,
    "name": "entity1"
}
GET localhost:8080/someEntities/1
{
    "id": 1,
    "name": "entity1"
}

EDIT 3 from spring boot v2.7 the classes annotated with @AutoConfiguration are migrated to a new file META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (nothe the final s so imports) see this question for more info. The configuration classes declared in spring.factories will still be honored

Pp88
  • 830
  • 6
  • 19
  • Thanks for the answer! Although it does not completely solve the problem (the set up is a bit more complicated), but it gives a good starting point. Thanks! – Pavlo Zasiadko Aug 30 '22 at 08:53
  • Keep in mind that you can always depend on older version of the libraries. I wrote the various steps in order to not refactor the pakages of the libraries, so less work – Pp88 Aug 30 '22 at 08:58