Java spring noob beans and configuration

Viewed 203

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?

3 Answers

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

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;

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

Related