2

I'm trying to build an Spring example application but I'm facing something strange. I'm using Netbeans 8.2 with a fresh Maven Java Application project. The only dependency (on the pom file) is spring-context 4.3.5.RELEASE (which triggers other necessary dependencies for the project).

I have the following simple main class in the main package (testdi):

public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

        Restaurant r = context.getBean(Restaurant.class);

        r.ListWaiters();
        r.ListTables();
    }

}

Config Class, which is in the same package as Main, contains the Spring Java Configuration, along with the creation of some Beans:

@Configuration
@ComponentScan(basePackageClasses=testdi.model.ComponentScan.class)
public class Config {
    @Bean
    public List<Waiter> waiters() {
        List<Waiter> waiters = new ArrayList<>();

        for(int i = 0; i < 6; i++) {
            waiters.add(waiter());
        }

        waiters.get(0).setName("Anna");
        waiters.get(1).setName("John");
        waiters.get(2).setName("Jane");
        waiters.get(3).setName("Bob");
        waiters.get(4).setName("Pam");
        waiters.get(5).setName("Lou");

        return waiters;
    }

    @Bean
    @Scope("prototype")
    public Waiter waiter() {
        return new Waiter();
    }

    @Bean
    public List<Table> tables() {
        List<Table> tables = new ArrayList<>();

        for(int i = 0; i < 5; i++) {
            tables.add(table());
            tables.get(i).setNumber(i + 1);
        }

        return tables;
    }

    @Bean
    @Scope("prototype")
    public Table table() {
        return new Table();
    }
}

Waiter and Table classes are pretty simple POJO classes just containing a property String name (for Waiter) and Int number (for Table), and resides in the testdi.model package. testdi.model.ComponentScan is just an Interface for basePackageClasses to work. The Restaurant class is also in the testdi.model package and is marked as component. Since it contains the most relevant code related to the problem, here it is:

@Component
public class Restaurant {
    @Autowired
    //@Qualifier("waiters")
    List<Waiter> waiters;

    @Autowired
    //@Qualifier("tables")
    List<Table> tables;

    public void ListWaiters() {
        System.out.println("List of waiters:");
        for(Waiter w : waiters) {
            System.out.println("Waiter " + w.getName());
        }
    }

    public void ListTables() {
        System.out.println("List of tables:");
        for(Table t : tables) {
            System.out.println("Table " + t.getNumber());
        }
    }
}

Now the problem: If I run the code as it is (with @Qualifier labels commented), waiters and tables fields contains ArrayList objects of size 1 with one object each initialized with default values (name=null for Waiter and number=0 for Table), but if I uncomment the @Qualifier labels, waiters and tables fields are correctly autowired to the Beans defined in the Config class. If I comment out the creation of the Waiter and Table List Beans on the Config class (and leave the @Qualifier labels commented out), the applications runs exactly as in the first case (ArrayList objects of size 1 ...).

It seems that Spring is creating List and List Beans on its own and using them as principal in case other Beans of the same class exists. I don't know if this is right but I was expecting the application to fail to run when I commented out the creation of this Beans on my Config class, since no beans of any of this two types should exist.

Am I understanding something wrong?

Regards.

Edit: Well, yes, I was understanding something wrong. According to this and this posts, this is the expected behavior of Spring. For me, it would be clearer Spring no auto creating collections for the declaration of collection variables and getting into the way of autowired; but well, at least I know how it works now.

As requested, here is the code for Waiter and Table classes:

public class Waiter {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Table {
    private int number;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}
Community
  • 1
  • 1
Vlad G.
  • 81
  • 1
  • 4
  • When you are injecting a typed list spring simply does a lookup for all beans of that type in the context. However as you are using `prototype` scope you will only get a single bean as that is what prototype does. Now if you add the qualifier Spring will actually look for a bean with that name to inject. – M. Deinum Jan 30 '17 at 18:53
  • Show the source of `Table` and `Waiter` – Grim Jan 30 '17 at 20:07
  • @M. Deinum yes, I just found out this is the expected behavior of Spring. I think its confusing since I wasn't expecting Spring to come in the way with a collection of its own and I would expect a NoSuchBeanDefinitionException for a bean I didn't created. – Vlad G. Jan 31 '17 at 00:34
  • Why wouldn't there be a bean... You have defined a prototype scoped bean so anyone which want a bean of type `Table` or `Waiter` receives a newly created instance. That is the behavior I would expect with a `prototype` scoped beans and created `prototype` scoped beans aren't part of the application context there is nothing to inject but a newly created bean. – M. Deinum Jan 31 '17 at 05:45
  • Of course Waiter and Table beans must be there since I'm defining them in Java Config class. What I wasn't expecting to exist, since there is no Bean or Component annotation for that kind of object, specifically on the scenario when the creation of the Waiter and Table List Beans are commented out, are the lists that Spring is creating on its own to inject on the Autowired variables on the Restaurant class. – Vlad G. Feb 01 '17 at 16:17

0 Answers0