Spring Boot: autowire beans from library project
Asked Answered
M

4

59

I'm struggling to autowire beans from my custom library, imported with gradle. after reading couple of similar topics I am still unable to find solution.

I have a Spring Boot project that depends on another project (my custom library with Components, Repositories etc...). This library is a Spring non-runnable jar, that consists primarily of domain Entities and Repositories. It doesn't have runnable Application.class and any properties...

When I start the application I can see that My 'CustomUserService' bean (from the library) is trying to be initialized, but the bean autowired in it failed to load (interface UserRepository)...

Error:

Parameter 0 of constructor in com.myProject.customLibrary.configuration.CustomUserDetailsService required a bean of type 'com.myProject.customLibrary.configuration.UserRepository' that could not be found.

I've even tried to set 'Order', to load it explicitly (with scanBasePackageClasses), scan with packages and marker classes, add additional EnableJPARepository annotation but nothing works...

Code example (packages names were changed for simplicity)

package runnableProject.application;

import runnableProject.application.configuration.ServerConfigurationReference.class
import com.myProject.customLibrary.SharedReference.class

//@SpringBootApplication(scanBasePackages = {"com.myProject.customLibrary", "runnableProject.configuration"}) 
//@EnableJpaRepositories("com.myProject.customLibrary")  

@SpringBootApplication(scanBasePackageClasses = {SharedReference.class, ServerConfigurationReference.class})   
public class MyApplication {

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

}

Classes from the library:

package com.myProject.customLibrary.configuration;

import com.myProject.customLibrary.configuration.UserRepository.class;

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private UserRepository userRepository;    

    @Autowired
    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;       
    }
...

package myProject.customLibrary.configuration;

@Repository
public interface UserRepository extends CustomRepository<User> {
    User findByLoginAndStatus(String var1, Status var2);

    ...
}
Martijn answered 23/1, 2017 at 21:21 Comment(0)
M
97

Just found the solution. Instead of defining base packages to scan from separate library, I've just created configuration class inside this library with whole bunch of annotation and imported it to my main MyApplication.class:

package runnableProject.application;    

import com.myProject.customLibrary.configuration.SharedConfigurationReference.class

@SpringBootApplication
@Import(SharedConfigurationReference.class)
public class MyApplication {

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

}
package com.myProject.customLibrary.configuration;

@Configuration
@ComponentScan("com.myProject.customLibrary.configuration")
@EnableJpaRepositories("com.myProject.customLibrary.configuration.repository")
@EntityScan("com.myProject.customLibrary.configuration.domain")
public class SharedConfigurationReference {}
Martijn answered 23/1, 2017 at 22:11 Comment(13)
yeah. the system will allow me to do it in 2 days.Martijn
2 days have already passed. You deserve the points. Your answer solved my problem after searching a bazillion of web sites.Arium
Ring ring ring! It's a friendly reminder to accept your own answer. stackoverflow.com/help/self-answerFriary
I think you should remove the '.class' part at the end of your importHarken
Accept your answer if possible, it solved my problem after digging for a while!Affected
very simple and clean answerVeriee
+1 However, if you want to avoid importing the configuration class. You can create a folder called 'META-INF' in the 'resources' folder of your library and add a file called 'spring.factories' with the content org.springframework.boot.autoconfigure.EnableAutoConfiguration=<fully_qualified_name_of_configuration_file> . This will autoconfigure your library.Despair
Does this solution work if the runnable application doesn't use spring framework?Ahimsa
@Ahimsa have you found a solution? I am planning to do something similar. I am thinking of using spring boot app (jar) in non spring boot application.Gustavo
@Gustavo Since my application isn't a Spring app and just a library, I just created a Factory which had methods to return an instance of the classes I neededAhimsa
Tried this solution but getting an error "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct".Airless
@Despair That should be an answer.Rienzi
@Rienzi I posted it as an answer :DDespair
D
15

You can create a folder called 'META-INF' in the 'resources' folder of your library and add a file called 'spring.factories' with the content org.springframework.boot.autoconfigure.EnableAutoConfiguration=<fully_qualified_name_of_configuration_file>. This will autoconfigure your library.

Despair answered 4/9, 2021 at 9:29 Comment(1)
The value accepts a comma separated list of all the Configurations, Components, etc. Easy solutionZwickau
C
13

Here's an answer updated for Spring boot 3 and boiled down to the essentials of autowiring library beans in another project "the easy way", where the library declares them and the other project does not have to know about something it has to import or specify in order to use them (once the library is a dependency in the first place).

First, make sure that your library includes .imports files from your resources folder. That means, for instance, that if you are using Maven then your pom file's <build> section's <resources> will contain a resource something like this:

<resource>
    <directory>src/main/resources</directory>
    <filtering>true</filtering>
    <includes>
        …
        <include>**/*.imports</include>
    </includes>
</resource>

This is important because Spring no longer supports the .factories file (which you may have been includeing before) but instead you should use src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports with, as its content, just your auto-configuration class(es) (one per line if you have more than one):

com.your.library.AutoConfigure

(If your resources folder is not at src/main/resources then replace that with the path to your resources folder everywhere it's mentioned in this answer.)

From there, two annotations appear to be essential on the auto-configuration class. One, the @AutoConfiguration that is part of the new autoconfiguration import system. (This replaces @Configuration on the autoconfiguration class specified in the imports file. Other configuration classes pulled in should continue using @Configuration, according to those docs.)

Two, @ComponentScan, will tell it to go make autowire-able beans out of all those Spring classes (@Component, @Service, @Controller, @Repository, @Bean) that would have been beans automatically under a @SpringBootApplication. This annotation can be further customized, but if you put the bare annotation on a class that is in the top-level package of your library, it should just find all the beans in that package (i.e. the whole library).

package com.your.library;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

@AutoConfiguration
@ComponentScan
public class AutoConfigure {
    // intentionally left empty
}

Congratulations! With these three files (well, the setting in the project file and these two other files) your library's beans should now be able to be @Autowired in services that use the library, without the service needing to treat it as if it were any different from its own local classes.

You may find that other annotations on your autoconfigure class are needed or otherwise helpful – for instance, configuring Repositories specifically; or if you have the AutoConfigure class in a com.your.library.configuration package then it could specify the base package in @ComponentScan("com.your.library") to pick up beans outside the configuration package. This answer is not intended to be an exhaustive coverage of what different Spring annotations do and all the ones that might be relevant to the question's code example; rather it is focused on the "missing piece" that the question is looking for: how to autowire those beans from a library in another project that depends on that library. (This question is the one that comes up if you google for how to autowire a library bean/component in Spring Boot, rather than any more general or basic version.)

From my testing these appeared to be the bare minimum pieces to get @Autowired working.

Combe answered 5/5, 2023 at 21:24 Comment(3)
(Apologies that I don't have a Gradle example of including the imports file, on hand. My experience is all Maven. If anyone familiar with the Gradle equivalent wants to edit that alternative in so the answer shows both, that would be great!)Combe
This may not have been the clearest version of this question to post this answer to. I answered the one that came up in my Google search. However, there are simpler versions of the question without the digression about configuring a @Repository for instance, one of which might be better to mark as the canonical and the rest as duplicates, and the best answers migrated to it. See https://mcmap.net/q/330974/-spring-bean-not-getting-autowired-from-custom-library/14216468 and https://mcmap.net/q/330975/-spring-autoconfigured-bean-in-dependency-library-is-not-loaded-in-spring-context/14216468 and https://mcmap.net/q/330976/-how-do-i-include-components-scanned-from-a-spring-library-into-the-main-application-consuming-that-library/14216468Combe
For me this is the perfect answer, seems to work flawlessly. Thank you for that. I'd like to add that mongo repos didn't work for me like that. To get that working I had to add @EnableMongoRepositories(basePackages = "...") to the @AutoConfiguration class you mention above. Maybe you can add that.Blinkers
P
6

The accepted answer is too cumbersome. What you would need to do is implement your own custom auto-configuration in your library jar so that it is picked up in the classpath scan in the main application. More details here

Purehearted answered 8/8, 2021 at 15:23 Comment(1)
I have a @ConfigurationProperties class in my custom starter, and the @Import in the accepted solution solved my problem. The properties are now injectable.Kaneshakang

© 2022 - 2024 — McMap. All rights reserved.