4

I'm trying to run the fallowing classes.

package com.example.demo;

import org.apache.catalina.core.ApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {

        var factory = new AnnotationConfigApplicationContext(AppConfig.class);
        test tst = factory.getBean(test.class);
        tst.getDummy().test();
        SpringApplication.run(DemoApplication.class, args);
    }

}

App config class:

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public test gettest(){
        return new test();
    }
//
    @Bean
    public test2 gettest2(){
        return new test2();
    }
}

First test class:

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Component
public class test{
    @Autowired
    test2 dummy;

    public void setDummy(test2 dummy) {
        this.dummy = dummy;
    }

    public test2 getDummy() {
        return dummy;
    }

    void test(){
        System.out.println("test");
    }
}

Second test class:

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class test2{

    public test2(){

    }
    void test(){
        System.out.println("test2");
    }

}

I get this error at run time

Field dummy in com.example.demo.test required a single bean, but 2 were found:
    - test2: defined in file [/Users/biliuta/IdeaProjects/demo/build/classes/java/main/com/example/demo/test2.class]
    - gettest2: defined by method 'gettest2' in class path resource [com/example/demo/AppConfig.class]

If i comment out the public test2 gettest2() bean from AppConfig I'll get this error:

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'gettest': Unsatisfied dependency expressed through field 'dummy'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.test2' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

How come in the first case test2 bean is found and when I comment it out from AppConfig it is unable to find it anymore (in the first error message you can clearly see that test2 is found)? Is @Configure annotation mandatory to add beans to the context?

Ori Marko
  • 56,308
  • 23
  • 131
  • 233
user1934513
  • 693
  • 4
  • 21

3 Answers3

4

When you define @Component you define a bean which can be found for autowiring

When you define @Bean (in `@Configuration) you define a bean which can be found for autowiring

So you define twice same bean which caused spring to failed to know which one is the correct bean to autowire

Solution is to define only one time

Ori Marko
  • 56,308
  • 23
  • 131
  • 233
1

how @Autowired works is it first try to find the required bean by type and a fallback mechanism is by bean name(ex: there are multiple beans for the same type)

but when you disable the @Bean annotation from AppConfig there should have been at least one bean of type Test2 as you have annotated Test2 with @Component annotation but it can't find it. To solve this you need to add @ComponentScan(basePackageClasses = { Test2.class }) on your AppConfig class as follows

@Configuration
@ComponentScan(basePackageClasses = { Test2.class })
public class AppConfig {
@Bean
public Test gettest(){
    return new Test();
}

//@Bean
public Test2 gettest2(){
    return new Test2();
}
}

but if you enable the bean annotation as follows

@Configuration
@ComponentScan(basePackageClasses = { Test2.class })
public class AppConfig {
@Bean
public Test gettest(){
    return new Test();
}

@Bean
public Test2 gettest2(){
    return new Test2();
}
}

you need to change the injection of Test2 on Test as by using

 @Autowired
 Test2 test2;

or by using

 @Autowired
 Test2 gettest2;
Tanmoy Majumdar
  • 448
  • 3
  • 7
  • yes this solves the issue but this is not what I don't understand. I'm missing more the big picture when it comes to how the spring manages beans. the question at the end of the post is why the test2 bean is not found anymore if I comment out public test2 gettest2() from AppConfig – user1934513 Jun 24 '20 at 10:22
  • @user1934513 does it answer your question now ? – Tanmoy Majumdar Jun 24 '20 at 12:18
  • Yes. Makes sense using the ComponentScan. Thanks. – user1934513 Jun 26 '20 at 07:16
1

There are a few ways of making your class a bean in Spring / Spring Boot

  • Annotating your class with @Component or any other derived annotation(@Service, @Repository, @Controller, @RestController)
  • Defining your class as a bean in XML configuration
  • Using @Bean annotation(this one can be applied only to a method specify that it returns a bean to be managed by Spring context). @Bean annotation is usually declared in Configuration classes methods

When Spring initializes its context, it looks for any bean definitions and creates beans. In your case, it finds duplicate declarations of both test and test2, so you need either to remove your configuration class or remove the @Component annotations from your classes.

See Spring Bean documentation for more information

vcmkrtchyan
  • 2,536
  • 5
  • 30
  • 59
  • if I remove the configuration class, what bean factory do I need to use in the main() function, instead of AnnotationConfigApplicationContext(AppConfig.class) ? Is there a default one which collects all the beans in the package? – user1934513 Jun 24 '20 at 11:46
  • Removing @Component annotations from classes works. – user1934513 Jun 24 '20 at 11:47
  • AnnotationConfigApplicationContext(test.class, test2.class) – user1934513 Jun 24 '20 at 11:57
  • Oh, I didn't notice you were creating an application context around it. You can either inject the context(which is really not a good idea), or just get rid of the beans, but keep the configuration – vcmkrtchyan Jun 25 '20 at 12:14