0

I am using spring for quite some time but this morning I came across with some unexpected behavior. As I could not decide by myself whether that was a desired functionality or a bug I am presenting it here with the hope I will get some good explanations about why would this be happening.

In short I have multiple beans defined in an application context and I create two map beans using utils:map name space with only part of my beans added to those maps. The two maps have exactly the same entries. Then I auto wire those maps. One auto wire is done using @Autowired annotation and the other one is done using @Resource

To my surprise the bean annotated with @Autowired had got all beans in the context not only the ones I specifically put in the map. The other one auto wired using @Resource annotation had only the expected two entries in it.

I am mainly interested in: 1. Why all the beans declared in my context of that time appear in the @Autowired map and not the ones I added 2. Why @Resource and @Autowired would behave differently

Here is the working code that reproduces the scenario described above.

Some interface here:

package my.testing.pkg;

public interface Foo {
    void doStuff();
}

And its implementation:

package my.testing.pkg;

public class FooImpl implements Foo {
    @Override
    public void doStuff() {}
}

The spring config file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:utils="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="foo_1" class="my.testing.pkg.FooImpl"/>
    <bean id="foo_2" class="my.testing.pkg.FooImpl"/>
    <bean id="foo_3" class="my.testing.pkg.FooImpl"/>
    <bean id="foo_4" class="my.testing.pkg.FooImpl"/>

    <utils:map id="fooMap1" map-class="java.util.HashMap">
        <entry key="foo_1" value-ref="foo_1"/>
        <entry key="foo_2" value-ref="foo_2"/>
    </utils:map>

    <utils:map id="fooMap2" map-class="java.util.HashMap">
        <entry key="foo_1" value-ref="foo_1"/>
        <entry key="foo_2" value-ref="foo_2"/>
    </utils:map>
</beans>

And the test case to reproduce the behavior:

package my.testing.pkg;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.util.Map;

import static org.junit.Assert.assertEquals;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/test-context.xml")
public class SpringMapCreatingTest {
    @Autowired
    private Map<String, Foo> fooMap1;
    @Resource
    private Map<String, Foo> fooMap2;

    @Test
    public void shouldInjectATwoEntriesMap() throws Exception {
        assertEquals("fooMap1 should have to entries", 2, fooMap1.size());
        assertEquals("fooMap2 map should have to entries", 2, fooMap1.size());
    }
}

Thank you in advance for your clarifications.

Julian
  • 3,678
  • 7
  • 40
  • 72

3 Answers3

1

What is happening there is the following:

  1. The @Autowired dependency will look for beans that match its type, and in this case it will create a map of your beans Foo mapped by their name. The same behavior will happen when you autowire on a list of beans, Spring will inject all the beans that implement the interface.
  2. The @Resource dependency will look first for a bean that matches the dependency name, e.g fooMap2 and will inject it.
nsanglar
  • 1,632
  • 1
  • 14
  • 24
0

Take a look at the documentation http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-autowired-annotation-qualifiers

Maps / List are not handled as "standard" beans injection.

Take a look to those questions on so: Can spring @Autowired Map? https://stackoverflow.com/a/13914052/1517816

HIH

Community
  • 1
  • 1
poussma
  • 7,033
  • 3
  • 43
  • 68
  • I accept your links as clarifying well enough the experienced behavior. But I believe putting more than the declared entries in an injected map can be quite a dangerous thing. Anyway a day you learn something new is a nice day. Thanks. – Julian May 22 '15 at 07:22
0

Check the spring docs, the tips section:

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

So it is better to use @Resource and not @Autowired for maps

Raghuveer
  • 2,859
  • 7
  • 34
  • 66