#ConditionalOnBean not detecting bean even though it is created - java

I have a class annotated with #Configuration and a bean
#Configuration
public class ExampleServiceConfig {
#Bean
#ConditionalOnProperty(value = servicefeature1.enable", havingValue = "true")
public ExampleServices exampleServices() {
return new ExampleServices();
}
I then have another service class that depends on the bean above:
#ConditionalOnBean(ExampleServices.class)
public class AnotherService{
#Autowired
public AnotherService(ExampleServices exampleServices){
this.exampleServices = exampleServices
}
}
In the spring debug logs, I see the first bean is getting created:
2020-02-28 14:08:51.841 DEBUG 18158 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'exampleServices'
But the AnotherService() is not getting created:
AnotherService:
Did not match:
- #ConditionalOnBean (types: x.x.x.ExampleServices; SearchStrategy: all) did not find any beans of type x.x.x.ExampleServices (OnBeanCondition)
Why is the AnotherService() not getting created even though the ExampleService bean was created successfully?
Also, I see the "did not match" log after the ExampleService bean got created.

I think adding #DependsOn to the mix could fix your issue. Here is another answer, somewhat similar with your problem: https://stackoverflow.com/a/50518946/6908551
#Component
#ConditionalOnBean(ExampleServices.class)
#DependsOn("exampleServices")
public class AnotherService {
#Autowired
public AnotherService(ExampleServices exampleServices) {
this.exampleServices = exampleServices
}
}

Related

spring boot custom starter, define entities in it, without using #EntityScan. Is it possible?

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
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 {
}
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.
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.

Does using #Import annotation create all beans defined in imported file?

A.java:
#Configuration
#Import(B.class)
class A {
#Bean
public X x(Y y) {
return new X(y);
}
}
B.java:
#Configuration
class B {
#Bean
public Y y() {
return new Y();
}
// And some other beans are defined
}
Will all beans defined in B.java be instantiated when bean x is instantiated? Since the file B.java is imported by A.java.
First I ran the sample that is posted by you.
Then I tested one sample with following variation,
#Configuration
#Import(B.class)
class A {
#Bean
public X x(Y y) {
return new X(y);
}
}
And then the last variation as following,
#Slf4j
#Configuration
class B {
#Bean
public Y y() {
log.info("Creating bean Y in B {}", System.currentTimeMillis());
return new Y();
}
#Bean
public Z z(){
log.info("Creating bean Z in B {}", System.currentTimeMillis());
return new Z();
}
// And some other beans are defined
}
Here is what I inferred from my observations,
A bean is always when you're using the annotation #Bean, bean is declared from a class which is annotated with either #Component, #Configuration or #Service and your component class is part of your declared package in #ComponentScan, then Spring will always register your bean. I can safely say that #Import has very little to do with bean creation.
Upon running second first(one with default constructor) multiple times, I found bean X created before bean Y.
Upon running second variation where I declared more than one bean in class B, the order of bean creation was Y, X and then Z.
#Import is just used to group your bean definition source as I found here on Baeldung,
#Import
A Test:
A simple test to visualize your answer:
class ATest {
private static final Logger LOGGER = LoggerFactory.getLogger(A.class);
private ApplicationContext ctx;
#BeforeEach
void setUp() {
ctx = new AnnotationConfigApplicationContext(A.class);
}
#Test
void test() {
for (String beanName: ctx.getBeanDefinitionNames()) {
LOGGER.info("BEAN: " + beanName);
}
}
}
Output:
[main] INFO be.dog.d.steven.A - BEAN: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
[main] INFO be.dog.d.steven.A - BEAN: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
[main] INFO be.dog.d.steven.A - BEAN: org.springframework.context.event.internalEventListenerProcessor
[main] INFO be.dog.d.steven.A - BEAN: org.springframework.context.event.internalEventListenerFactory
[main] INFO be.dog.d.steven.A - BEAN: a
[main] INFO be.dog.d.steven.A - BEAN: org.company.B
[main] INFO be.dog.d.steven.A - BEAN: y
[main] INFO be.dog.d.steven.A - BEAN: x
As you can see, #Import did make the application context include all beans in class B as expected.
The first 4 beans are always created, even if you use just one empty class annotated with #Configuration to build the application context. The fifth bean that will always be created is the annotated class used to create the context.
In practice:
Although this works fine, it is cleaner to have your application do a #ComponentScan(basePackages = "org.company") in a certain package or folder. This way you do not always have to add new components to the #Import annotation. (You might also not want to scan the whole project when you are working on a bigger project, rather just folders containing actual components.)
You can however use #Import on test classes to only load an isolated part of the context for that test.
You can also set the logging level of the Spring Framework to DEBUG in the application.properties:
logging.level.org.springframework=DEBUG
This way you can also check the logs of your application to see what beans get created.

Error creating bean because it's an interface?

Basically I have two beans implementing the same interface. One is for profile "default" and another "integration".
public interface SomeClientIfc { ... }
#Component
#Profile(value={"functional", "integration"})
public class StubSomeNIOClient implements SomeClientIfc {...}
public class SomeNIOClient implements SomeClientIfc {...}
#Configuration
#Profile("default")
public class SomeClientConfiguration {
#Bean
public SomeClientIfc someClient() {
...
SomeNIOClient someClient = new SomeNIOClient(numberOfParititions, controllerHosts, maxBufferReadSize,
connectionPoolSize);
return someClient;
}
}
In prod code it's
#Autowired
public SomeUserResolver(..., SomeClientIfc someClient) {...}
So far so good and I did see the stub bean is called in an integration test. Then I want to inject some test data into the stub bean in my integration test:
#ContextConfiguration(locations = {"/configProperties.xml", "/integrationTests.xml", ...})
#ActiveProfiles("integration")
public class SomeTestBase {
#Autowired
private SomeClientIfc someClientIfc;
}
However, when running the test I got error message
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'someClientIfc': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.audiencescience.some.client.SomeClientIfc]: Specified class is an interface
I even tried to replace SomeClientIfc with StubSomeNIOClient but still get the same message, even though StubSomeNIOClient is not an interface.
You should add a Qualifier annotation along with the Autowired one to specify which bean must be instantiated:
#Autowired
#Qualifier("my-bean")
The reason it's trying to inject the SomeClientIfc is because you called the variable 'someClientIfc'.
In the integration environment you have all 3 classes initialized: SomeClientIfc, StubSomeNIOClient, and SomeNIOClient. This creates a confusion for spring, luckily there are ways to resolve that confusion.
One way is as mentioned above by Little Santi, another way is to name your variable 'stubSomeNIOClient' see code below
#ContextConfiguration(locations = {"/configProperties.xml", "/integrationTests.xml", ...})
#ActiveProfiles("integration")
public class SomeTestBase {
#Autowired
private SomeClientIfc stubSomeNIOClient;
}

Does Spring #Autowired inject beans by name or by type?

I am reading beginning spring (wiley press) book. In chapter 2 there is an example
about Java configuration and #Autowired. It provides this #Configuration class
#Configuration
public class Ch2BeanConfiguration {
#Bean
public AccountService accountService() {
AccountServiceImpl bean = new AccountServiceImpl();
return bean;
}
#Bean
public AccountDao accountDao() {
AccountDaoInMemoryImpl bean = new AccountDaoInMemoryImpl();
//depedencies of accountDao bean will be injected here...
return bean;
}
#Bean
public AccountDao accountDaoJdbc() {
AccountDaoJdbcImpl bean = new AccountDaoJdbcImpl();
return bean;
}
}
and this regular bean class
public class AccountServiceImpl implements AccountService {
#Autowired
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
...
}
When I run the code, it works. But I expected an exception because I have defined 2 beans with the same type in the configuration.
I realized it works like this:
if Spring encounters multiple beans with same type it checks field name.
if it finds a bean with the name of the target field, it injects that bean into the field.
Isn't this wrong? Is there a bug in Spring's handling of Java configuration?
The documentation explains this
For a fallback match, the bean name is considered a default qualifier
value. Thus you can define the bean with an id "main" instead of the
nested qualifier element, leading to the same matching result.
However, although you can use this convention to refer to specific
beans by name, #Autowired is fundamentally about type-driven injection
with optional semantic qualifiers. This means that qualifier values,
even with the bean name fallback, always have narrowing semantics
within the set of type matches; they do not semantically express a
reference to a unique bean id
So, no, it's not a bug, that is the intended behavior. The bean id (name) will be used as a fallback if a by-type autowiring doesn't find a single matching bean.

Can Spring Autowire beans created in a BeanFactoryPostProcessor

I have a standard bean with some properties that need to be autowired.
#Service
public class MyServiceImpl implements MyService {
#Autowired
private FirstRepository first;
public MyServiceImpl() {
}
I use a Java Config to find the beans:
#Configuration
#ComponentScan(basePackages = "com.company", excludeFilters = { #Filter(Configuration.class) })
public class MainConfig {
}
However, the FirstRepository Bean doesn't exist so I create it in a BeanFactoryPostProcessor:
public class RepoGeneratorPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
GenericBeanDefinition jpaR = new GenericBeanDefinition();
jpaR.setBeanClass(JpaRepositoryFactoryBean.class);
jpaR.setAutowireCandidate(true);
jpaR.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
jpaR.setLazyInit(false);
jpaR.setPropertyValues(new MutablePropertyValues().add("repositoryInterface", FirstRepository.class));
RootBeanDefinition definition = new RootBeanDefinition();
definition.setBeanClass(FirstRepository.class);
definition.setAutowireCandidate(true);
definition.setFactoryBeanName("&jpaR");
definition.setFactoryMethodName("getObject");
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
definition.setLazyInit(false);
definition.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory;
registry.registerBeanDefinition("jpaR", jpaR);
registry.registerBeanDefinition("first", definition);
}
When I start my application I get the following exception which seems to suggest that Spring can't find the FirstRepository bean.
org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.company.FirstRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.
If I remove the #Autowired annotation I can see after start up that the FirstRepository bean is properly created.
Any suggestions?
This exception is saying that there is no bean defined for the FirstRepository class when the project is being built. Which I cannot see it here either.
The simplest solution would be to have a bean definition in your application-context.xml like this:
<bean id="firstRepository" class="your.package.FirstRepository" autowire="byName"/>
In this case, at the start up, there will be that bean definition.
I don't think you need the & before the beanname in
definition.setFactoryBeanName("&jpaR");
I used something like that in my project
definition.setFactoryBeanName("jpaR");
and it worked as expected
The & is needed if you need to get the factory bean of the bean named first.
&first should return jpaR.
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-extension-factorybean

Categories