Why is spring not injecting using generic qualifiers? - java

this is my configuration class
#Configuration
class Factories {
#Bean
Collection<Log> logs() {
List<Log> logs = new ArrayList<>();
for ( int i = 0; i < 10; i++ ) { // gross example code
Log log = new Log();
log.setEntry( RandomStringUtils.randomAlphabetic( 10 ) );
logs.add( log );
}
return logs;
}
}
and here's how I'm trying to autowire it
#Service
#Transactional
public class LogService {
private final LogRepository repository;
private final ObjectFactory<Instant> now;
private final Collection<Log> logs;
#Autowired
LogService( final LogRepository repository, final ObjectFactory<Instant> now, final Collection<Log> logs ) {
this.repository = repository;
this.now = now;
this.logs = logs;
}
but I get this exception
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xenoterracide.example.log.Log] found for dependency [collection of com.xenoterracide.example.log.Log]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1326)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1024)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:967)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
... 24 more
according to the documentation I think this should work. Here's my full code in case you need it. Am I misunderstanding the documentation?

Given #Autowired a List<Something>, Spring will look for as many Something beans as you have defined in your ApplicationContext and attempt to autowire them all into the target. The documentation states
As a specific consequence of this semantic difference, beans that are
themselves defined as a collection or map type cannot be injected
through #Autowired, because type matching is not properly applicable
to them. Use #Resource for such beans, referring to the specific
collection or map bean by unique name.
In your case, you have
#Autowired
LogService(/* ... */ final Collection<Log> logs ) {
but no Log beans. So it complains
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.xenoterracide.example.log.Log] found for dependency [collection of com.xenoterracide.example.log.Log]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
What you seem to want is a bean of type Collection<Log> injected directly. Spring can do this with the javax.annotation.Resource annotation. Unfortunately, that annotation doesn't work on constructors.
You'll need to either annotate your (changed to non-final) field or add a setter method and annotate that.
#Resource
private Collection<Log> logs;
or
#Resource
public void setLogs(Collection<Log> logs) {
this.logs = logs;
}

Related

Autowire class with arguments in constructor fails

I have the following class :
public class ProducerWrapper<K, V> {
Producer<K, V> producer;
ThreadPoolExecutor threadPoolExecutor;
#Autowired
public ProducerWrapper(Properties p, int poolsize) {
......
log.info("Created kafka producer");
}
....
I try to inject it in a different service :
#Service
public class mainService{
#Qualifier("ProducerX")
#Autowired
private ProducerWrapper<Long,CustomObject1> p1;
#Autowired
#Qualifier("ProducerY")
private ProducerWrapper<Long,CustomObject2> p2;
And I created the following configuration :
#Configuration
#ComponentScan("main_package..")
public class MyConf {
#Bean(name = "ProducerX")
public ProducerWrapper<Long, CustomObject1> createProducerWrapper() throws IOException {
FileInputStream propertiesFile = new FileInputStream("producerx.properties");
properties = new Properties();
properties.load(propertiesFile);
return new ProducerWrapper<>(properties,5);
}
#Bean(name = "ProducerY")
public ProducerWrapper<Long, CustomObject2> createProducerWrapper() throws IOException {
FileInputStream propertiesFile = new FileInputStream("producery.properties");
properties = new Properties();
properties.load(propertiesFile);
return new ProducerWrapper<>(properties,5);
}
}
As you can see I have a different properties file for each producer. The error I'm getting is the following :
Error creating bean with name 'ProducerWrapper' defined in file [..../ProducerWrapper.class]: Unsatisfied dependency expressed through constructor parameter 1;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'int' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Parameter 1 of constructor in com.xx.xx.ProducerWrapper required a bean of type 'int' that could not be found.
If I remove the autowired annotation on top of the constructor, I'm getting a different error that the default constructor can't be found by Spring.
In addition, in the logs I see the following message that indicates that everything in the constructor was run :
2020-06-24 12:14:49.331 INFO 30912 --- [ main] c.a.a.ProducerWrapper : Created kafka producer
What am I doing wrong ?
You have this signature:
#Autowired
public ProducerWrapper(Properties p, int poolsize) {
...
You have not provided the "poolsize" parameter to be autowired. i.e. there's no integer that exists in your config that can be autowired into this variable.
To resolve this: Create a PoolSize class that wraps an int value. Then create a PoolSize object in your config to be autowired.
It says in the error output:
...No qualifying bean of type 'int' available: expected at least 1 bean which qualifies as autowire candidate.
I found the solution in the following stackoverflow post
Bottom line :
The constructor in the ProducerWrapper class shouldn't have any annotation :
//None annotation above constructor
public ProducerWrapper(Properties p, int poolsize) {
......
log.info("Created kafka producer");
}
Create a configuration class for the beans just like I pasted in my main comment.
3.Remove the Service annoatipn from the ProducerWrapper

How to autowire a collection of a particular size

I am trying autowire a list of objects for a particular size but inspite searching through all stackoverflow related questions I am unable to see anything like this discussed before.I am surprised that this would never be needed?
In below example I see that the size of myListeners is always 1. How do i make myListeners list having 10 instances of MyListener
#Component
#Scope("prototype")
public class ListenerThreadPool {
private static final Logger LOGGER = LoggerFactory.getLogger(ObjectPool.class);
private ExecutorService threadPool;
private int poolSize;
#VisibleForTesting
final AtomicBoolean isStarted = new AtomicBoolean(false);
#Autowired
public void setMyObject(List<MyListener> myListeners) {
this.myListeners = myListeners;
}
#Autowired
#VisibleForTesting
List<MyListener> myListeners;
public void setPoolSize(int poolSize) {
this.poolSize=poolSize;
this.threadPool = Executors.newFixedThreadPool(poolSize);
}
public void start() {
if (isStarted.compareAndSet(false, true)) {
for (int i = 0; i < myListeners.size(); ++i) {
LOGGER.info("Starting listeners pool " + myListeners.get(i).toString());
threadPool.submit(myListeners.get(i));
}
} else {
LOGGER.warn("Cannot start listeners pool because it's already started");
}
}
}
I tried something like below but I get error
#Bean
public List listMyListeners(#Value("${workflow.threads:10}") int threads) {
return new ArrayList(10);
}
#Autowired
#Qualifier("listMyListeners")
List myListener;
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: java.util.List com.groupon.mailman.messaging.MyListenerPool.myListener; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.groupon.mailman.messaging.MyListener] found for dependency [collection of com.groupon.mailman.messaging.MyListener]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true), #org.springframework.beans.factory.annotation.Qualifier(value=listMyListeners)}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.groupon.mailman.messaging.MyListener] found for dependency [collection of com.groupon.mailman.messaging.MyListener]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true), #org.springframework.beans.factory.annotation.Qualifier(value=listMyListeners)}
If you're using #Autowired for java.util.List it will inject a list of all Java Beans matching the List’s Generic type.
So, obviously, you need more beans of type MyListener in your context(now it's only one).

Spring can't autowire class

I have the following Spring #Configuration:
#Bean
#Qualifier("mongo")
public MongoFacade mongo(Environment env){
final String host = env.getProperty("database.host");
final MongoClient mongoClient = new MongoClient(host);
return new MongoFacade(mongoClient, "test-db");
}
#Bean
public MessageStore<Event> eventStore(#Qualifier("mongo") MongoFacade mongo, ObjectMapper mapper) {
return new MongoMessageStore<>(mongo, mapper);
}
When I launch my application an exception is thrown and the cause is:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'eventStore' defined in it.m.MyConfiguration: Unsatisfied dependency expressed through constructor argument with index 0 of type [base.backend.persistence.impl.mongo.MongoFacade]: : No qualifying bean of type [base.backend.persistence.impl.mongo.MongoFacade] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Qualifier(value=mongo)}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [base.backend.persistence.impl.mongo.MongoFacade] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Qualifier(value=mongo)}
MongoFacade class is a real class, and don't implements interfaces, I've also tried to remove the Environment dependency from the method, but still doesn't work.
Removing the #Qualifier annotation the error is:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [base.backend.persistence.impl.mongo.MongoFacade] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
MongoFacade is a simple facade for MongoClient class that works with my PersistenceId class:
public class MongoFacade {
public static final String DEFAULT_NAME = "default";
private final MongoClient mongo;
private final String defaultName;
public MongoFacade(MongoClient mongoClient) {
this(mongoClient, DEFAULT_NAME);
}
public MongoFacade(MongoClient mongoClient, String defaultName) {
this.mongo = mongoClient;
this.defaultName = defaultName;
}
public Document findOne(PersistenceId id){
final MongoCollection<Document> collection = collection(id);
final String documentId = documentId(id);
return collection.find(eq("_id", documentId)).first();
}
public UpdateResult updateOne(PersistenceId id, Document update) {
final MongoCollection<Document> collection = collection(id);
final String documentId = documentId(id);
return collection.updateOne(eq("_id", documentId), update);
}
The environment can be injected as a field instead of the constructor also I would suggest using method calls instead of auto wiring for the dependencies.
Something like the following
#Autowired
private Environment env;
#Bean
public MongoFacade mongoFacade(){
final String host = env.getProperty("database.host");
final MongoClient mongoClient = new MongoClient(host);
return new MongoFacade(mongoClient, "test-db");
}
#Bean
public MessageStore<Event> eventStore(ObjectMapper mapper) {
return new MongoMessageStore<>(mongoFacade(), mapper);
}
Edit: Added Spring Boot MongoDB config
From the comments it is clear you are using Spring Boot, this already auto configures a MongoClient for you. Instead of doing it yourself you can reduce your configuration a little further. (Se also MongoAutoConfiguration).
In your application.properties add
spring.data.mongodb.host=<value of current database.host>
or if it is a full URI
spring.data.mongodb.uri=<value of current database.host>
Then change your configuration class.
#Bean
public MongoFacade mongoFacade(MongoClient mongoClient){
return new MongoFacade(mongoClient, "test-db");
}
#Bean
public MessageStore<Event> eventStore(ObjectMapper mapper) {
return new MongoMessageStore<>(mongoFacade(null), mapper);
}
Spring Boot will now construct a MongoClient.
can you please use #Service instead of #qualifier while declaring bean definition
#Service("mongo")
public MongoFacade mongoFacade()
let me know if it worked for you or not .

Why spring can't find my bean?

I created an interface and a class:
public interface UserService {
List<User> listAll();
}
#Transactional
public class DefaultUserService implements UserService {
private String tableName;
public List<User> listAll() { someDao.listAllFromTable(tableName); }
public void setTableName(String tableName) { this.tableName = tableName; }
}
Also in my application context xml file context.xml, I defined:
<bean id="userService" class="mypackage.DefaultUserService">
<property name="tableName" value="myusers" />
</bean>
Then I want to test the DefaultUserService:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:context-test.xml"})
#TransactionConfiguration(transactionManager = "testTransactionManager")
#Transactional
public class UserServiceTest {
#Autowired
private DefaultUserService userService;
#Before
public void setup() {
userService.setTableName("mytesttable");
}
#Test
public void test() {
// test with userService;
userService.listAll();
}
}
Notice it uses context-test.xml, which imported the original context.xml:
<import resource="classpath:context.xml"/>
Unfortunately, when the test starts, spring throws exception:
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'mypackage.UserServiceTest':
Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire field:
private mypackage.DefaultUserService mypackage.userService
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [mypackage.DefaultUserService] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I'm not sure where is wrong, why spring can't find the bean DefaultUserService I defined?
It's because #Transactional places the bean is behind a jdk proxy implementing UserService interface, after that the bean is only available as UserService and not DefaultUserService.
See https://stackoverflow.com/a/18875681/241986.
You can try setting the table name with a property placeholder #Value("${someprop}") and define that property in test context, or create another interface that will expose setTableName(), and autowire that helper interface into the test case.
I'm not sure there are any easy solutions of the problem, I think this task can be subsumed under the problem of bean redefinition in Spring test-context framework
Spring beans redefinition in unit test environment
Try to replace the class DefaultUserService to the interface UserService
public class UserServiceTest {
#Autowired
private UserService userService;
....
}
You have not defined the getter for your property tableName in your implementing class.Spring IOC container works on the POJO model

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