Cannot load bean in different package in Spring Boot application - java

I'm trying to load beans from a different package on a Spring boot application. Here's my main class, that lives in com.xyz.app package:
Application.java:
#SpringBootApplication(scanBasePackages = { "com.xyz.app.repository" })
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(Application.class, args);
context.getBean(MyInterfaceRepository.class).loadData();
}
The interface MyInterfaceRepository.java is inside the com.xyz.app.repository package, and is defined as follows:
#RepositoryRestResource(collectionResourceRel = "aas", path = "aas")
public interface MyInterfaceRepository extends MongoRepository<MyClass, Long>,
MyCustomInterface {
...
}
Then, I also have a MyInterfaceRepositoryImpl.java, that lives in com.xyz.app.repository, that provides an implementation for MyCustomInterface.java, that also lives in com.xyz.app.repository.
Starting my application I get the following:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xyz.app.repository.MyInterfaceRepository] is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:372)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1066)
at com.xyz.app.Application.main(Application.java:60)
I already checked, and indeed if I put MyInterfaceRepository.java and MyInterfaceRepositoryImpl.java in the same package as Application.java, com.xyz.app, than it works.
It seems that Spring is not able to load the beans from a different package than the one where Application.java is.
Also, I tried replacing the #SpringBootApplication with these:
#Configuration
#EnableAutoConfiguration
#ComponentScan({"com.xyz.app.repository"})
Same issue.. Any idea?

if you are using spring mongodb, you don't need to use annotation: #RepositoryRestResource(collectionResourceRel = "aas", path = "aas")
You also can use #Autowired in the MyInterfaceRepository.

On your application class/main class add #EnableMongoRepositories. You should have something like this:
#SpringBootApplication(scanBasePackages = {"com.xyz.app"})
#EnableMongoRepositories(basePackages = {"com.xyz.app.repository"}, repositoryBaseClass = MyInterfaceRepositoryImpl.class)
public class Application extends SpringBootServletInitializer {
...
}
See EnableMongoRepositories for more details about the annotation.

Related

Injecting library class as dependencies in spring project

I have multiple library classes in my project which need to be injected into a service class. This is the error statement for IntegrationFactory class:
Consider defining a bean of type 'com.ignitionone.service.programmanager.integration.IntegrationFactory' in your configuration.
This error is coming on almost every injection where this library class is injected.
I have already added the Library package in #ComponentScan, but, as it is read-only file, I can not annotate the library class. I came to know from some answer here that Spring can not inject classes which it does not manage. This library is not built on spring.
I have tried to create a #Bean method which returns the IntegrationFactory(class in question) in the class where #Inject is used, but this too does not seem to work.
How can this be done, preferably without creating a stub/copy class?
This is EngagementServiceImpl class snippet:
#Inject
public EngagementServiceImpl(EngagementRepository engagementRepository,
#Lazy IntegrationFactory integrationFactory, TokenRepository tokenRepository,
EngagementPartnerRepository engagementPartnerRepository, MetricsService metricsService) {
this.engagementRepository = engagementRepository;
this.integrationFactory = integrationFactory;
this.tokenRepository = tokenRepository;
this.engagementPartnerRepository = engagementPartnerRepository;
this.metricsService = metricsService;
}
This is injection part:
#Autowired
private EngagementService engagementService;
This is ConfigClass:
#Configuration
public class ConfigClass {
#Bean
public IntegrationFactory getIntegrationFactory(){
Map<String, Object> globalConfig = new HashMap<>();
return new IntegrationFactory(globalConfig);
}
#Bean
#Primary
public EntityDataStore getEntityDataStore(){
EntityModel entityModel = Models.ENTITY;
return new EntityDataStore(this.dataSource(), entityModel );
}
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
You need to add your bean definitions in a configuration class.
#Configuration
public class ServiceConfig {
#Bean
public IntegrationFactory getIntegrationFactory(){
// return an IntegrationFactory instance
}
}
Then you have to make sure your #Configuration class gets detected by Spring, either by having it within your scanned path or by manually importing it via #Import from somewhere withing you scanned path. An example of #Import, considering you are using Spring Boot.
#Import(ServiceConfig.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Hope this helps!
Your Bean IntegrationFactory can't be found, as it is not annotated with any Spring stereotype and therefore not recognized by the component scan.
As you have multiple options to provide an instance of your class to the application context, read the Spring documentation (which also includes samples) to find out which one fits you the most:
https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
One Option would be to create a factory which provides an instance of your class to the application context, like it is stated in the documentation:
#Configuration
public class AppConfig {
#Bean
public IntegrationFactory myIntegrationFactory() {
return new IntegrationFactory();
}
}
Do not forget to add the Configuration to the application context.

Spring Boot version update breaks repository

See edit at the end...
I updated the dependencies of an app from 1.5.3-RELEASE to 1.5.9-RELEASE
***************************
APPLICATION FAILED TO START
***************************
Description:
Field mrepo in com.test.svc.TestHandler required a bean of type 'com.test.data.MongoTestRepository' that could not be found.
Action:
Consider defining a bean of type 'com.test.MongoTestRepository' in your configuration.
My Application.java is at com.test.Application and has the usual stuff:
#ServletComponentScan
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
My TestHandler is a #Service
#Service
public class TestHandler {
#Autowired
private MongoTestRepository mrepo;
...
}
My MongoTestRepository is a #Repository interface that operates on MongoTest classes
#Repository
public interface MongoTestRepository extends MongoRepository<MongoTest, String> {
}
So, with the older version of the spring-boot packages (1.5.3-RELEASE) everything worked fine. Updating to 1.5.9-RELEASE and it builds but gives me the above error on deployment/startup.
What's going on here? Is there some new annotation that I'm missing? Why would a minor version update suddenly break this?
EDIT=I am wrong, it's an update to the spring-data-mongodb package that broke it. It was updated from 1.10.9-RELEASE To 2.0.2-RELEASE at the same time and I didn't notice right away.

JavaConfig Spring make beans available to all application

I have a jar with a main method. I created a java config with the #Configuration annotation.
#Configuration
#ComponentScan(basePackages = { "com.test.commons" })
public class ProxyConfig {
}
In this com.test.commons I have put a service
package com.test.commons;
#Service
public class RestService {
//do rest calls
public String restGetCall(URL url){
...
}
}
I am NOT asking for this
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ProxyConfig.class);
context.getBean("myBean");
The main
#SpringBootApplication(scanBasePackages={"com.test.commons", "com.test.soapproxy" })
public class MainAppProxy
{
private final static Logger logger = LoggerFactory.getLogger(MainAppProxy.class);
public static void main( String[] args )
{
SpringApplication.run(MainAppProxy.class, args);
// Choose environment from the arguments
String env = args[0];
// publish an endpoint here
Configuration config = null;
config = configs.properties
(new File("config_"+env+".properties"));
Endpoint.publish(endpointAddress, new SomeProxyImpl(config));
The class in which I am trying to inject the bean (is the #Component needed here really?)
#Component
public class SomeProxyImpl implements SomeServiceSoap {
#Autowired RestService restService;
I would like to be able to inject this RestService bean through #Autowired in all my application, not only in SomeProxyImpl(which is not working anyway). How can I do that?
Spring don't autowire field created by a new, unless you ask for it, like this : ApplicationContextHolder.getContext().getAutowireCapableBeanFactory().autowireBean(object);
If your SomeProxyImpl class is in the "com.test.soapproxy" package, and your class is annotated with #Component, then Spring must have created an instance with the bean autowired.
You should then get this bean from your context and use it instead of creating a new one.

#ComponentScan doesn't work in Spring boot AutoConfiguration class?

I am trying to create a new starter. I have a business module, say ProjectManager, that contains some classes annotated with #Component. Following the tutorial, I created an autoconfigure module, it contains an AutoConfiguration class. Firstly, I tried to use #ComponentSan to find the beans in my business module.
#ComponentScan(value = {"com.foo.project"})
#ConditionalOnClass({Project.class})
#Configuration
public class ProjectAutoConfiguration {
....
}
But it doesn't work. I have to add additional configuration class as below:
#Configuration
#ComponentScan(value = {"com.foo.project"})
#MapperScan(value = {"com.foo.project"})
public class ProjectConfig {
}
And then import it into AutoConfiguration class like below:
#Import(ProjectConfig.class)
#ConditionalOnClass({Project.class})
#Configuration
public class ProjectAutoConfiguration {
....
}
That works. But according to the spring doc.
auto-configuration is implemented with standard #Configuration classes
So my question is, Why #ComponentScan doesn't work here ? Did I make something wrong? Or it is by design ?
you have to use the compentscan annotation into the main class. Here a sample code:
#SpringBootApplication
#ComponentScan("com.foo.project")
public class MainApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MainApplication.class);
}
public static void main(String[] args) {
new MainApplication().configure(new SpringApplicationBuilder(MainApplication.class)).run(args);
}
}
Cheers
Automatic everything requires the Application class (annotated with #SpringBootApplication) to be in a "higher" package than the components you want to scan.
Use:
package com.example.foo;
for your application and put components in a package like:
package com.example.foo.entities;
See also https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-using-springbootapplication-annotation.html
Can you try with following?
#ConditionalOnClass({Project.class})
#Configuration
#EnableAutoConfiguration
#ComponentScan(value = {"com.foo.project"})
public class ProjectAutoConfiguration {
....
}
I was developing a SDK project. It needs the application which depends on the SDK to scan for beans beneath specific package in the SDK during start period.
Anotate with #ComponentScan on autowire configuration class doesn't take effect.
Then I am trying to use #Import annotation to import a class implemented interface ImportBeanDefinitionRegistrar(Interface to be implemented by types that register additional bean definitions when processing #Configuration classes. Useful when operating at the bean definition level (as opposed to #Bean method/instance level) is desired or necessary).
Within ImportBeanDefinitionRegistrar implementation class, I register a class annotated with #ComponentScan as bean. Run application again, it works as expected.
Codes below:
AutoConfiguration Class:
#Configuration
#Import(TestConfigRegistar.Registrar.class)
public class TestClientAutoCofiguration {
}
Registar class:
public class TestConfigRegistar {
public static class Registrar implements ImportBeanDefinitionRegistrar {
private static final String BEAN_NAME = "componentScanConfig";
#Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,
BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(ComponentScanConfig.class);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
}
}
}
Class with #ComponentScan annotation
// Leave an empty value of #ComponentScan will let spring scan
// current class package and all sub-packages
#ComponentScan
public class ComponentScanConfig {
}
I believe that the point is that beans annotated with #ComponentScan must be defined at definition level (as opposed to #Bean method/instance level). Please correct me if I'm wrong, Thanks.

How can I programmatically configure Spring's #Configuration annotations?

Specifically, I would like to be able share configuration classes by instantiating them and including them. Where you would normally do this:
#Configuration
#Import({SharedConfiguration.class})
public class MyAppContext extends WebMvcConfigurerAdapter {
//stuff
}
#Configuration
#ComponentScan("com.example")
public class SharedConfiguration {
//stuff
}
I would like to do this:
#Configuration
public class MyAppContext extends WebMvcConfigurerAdapter {
public SharedConfiguration sharedConfig(){
return new SharedConfiguration("com.example");
}
//stuff
}
#Configuration
public class SharedConfiguration {
public SharedConfiguration(String package){
//tell Spring to scan package
}
}
The reason for this is that I need to be able to tell the shared component doing the scan what package to look at. It will different depending on what project it is being used in.
EDIT:
To provide some additional context, I'm trying to make a general-use configuration for setting up Hibernate and EHCache using our external configuration provider that several projects can use. I'm certainly open to other methods to doing this, but this seemed like the most logical path to me. I'm sure there's ~something~ in Spring that I can fiddle with to say, "Here! Scan this path when Spring initializes you!" instead of hard-coding it into an annotation.
You can take advantage of property sources in this case.
In the test case, I am setting a system property that is picked up by the Spring property source configuration -
#RunWith(SpringRunner.class)
#ContextConfiguration
public class MyAppContextTest {
#Autowired
ApplicationContext context;
#BeforeClass
public static void beforeClass() {
// use a system property to configure the component scan location of the SharedConfiguration
// where the "ExampleBean" lives
System.setProperty("packages", "net.savantly.other.packages");
}
#Test
public void ensureExampleBeanExists() {
// throws exception if it doesnt exist
context.getBean(ExampleBean.class);
}
#Configuration
#Import(MyAppContext.class)
static class TestContext {
}
}
Using the Spring expression language in the ComponentScan -
#Configuration
#ComponentScan("${packages}")
public class SharedConfiguration {}
Other Referenced Classes -
#Configuration
#Import(SharedConfiguration.class)
public class MyAppContext extends WebMvcConfigurerAdapter {
#Autowired
SharedConfiguration sharedConfig;
//stuff
}
#Service
public class ExampleBean {
}

Categories