0

Let's imagine I have the next classes in the project based on Spring framework:

interface I {
    String getName()
}

@Component
class I1 implements I {
    @Override
    String getName() {return "I1"}
}

@Component
class I2 implements I {
    @Override
    String getName() {return "I1"}
}

And I want to gather them all in the map using the @Autowired method:

@Component
public class A {
    private Map<I> map = new HashMap<>()

    @Autowired
    public registerI(I i) {
        map.put(i.getName(), i)
    }
}

Should I make this method registerI synchronized? I mean, can Spring call this method in several threads simultaneously? Or this method will be called sequentially?

Thanks

4 Answers4

3

You don't have to use synchronized because Spring bean initialization is single-threaded and thread-safe. You can think of gotchas like thread-scoped or lazy beans but for regular singleton beans initialization happens in one thread.

You might want to use synchronized to make sure that after registerI() method is called your object is safely published, although auto-wired constructor with final field is more readable.

@Component
public class A {
    private final Map<String, I> map;

    public A(List<I> list) {
        map = list.stream().collect(Collectors.toMap(I::getName, i -> i));
    }

}
Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
  • Thanks, for the response. Can you provide a link on documentation, where is said, that spring bean initialization is single-threaded? – Dmitro Liakhov Dec 17 '19 at 20:19
  • There is [Parallel bean initialization during startup \[SPR-8767\] #13410](https://github.com/spring-projects/spring-framework/issues/13410) which suggested to implement the multi-threaded singleton initialization. It was discussed but never implemented. – Karol Dowbecki Dec 17 '19 at 20:46
1

You will get an exception during app startup because Spring cannot determine the correct implementation of interface "I" what you want to inject. You should use @Qualifier.

If you want to accomplish that scenario, this should be enough.

@Component
    public static class A {
        private Map<String,I> map = new HashMap<>();

        public A(List<I> list) {
            //map = list.stream().collect(Collectors.toMap(I::getName, x -> x));
            for (I i : list) {
                map.put(i.getName(), i);
            }
        }
    }

You will end with only one value in the map.

The commented line works if there are not duplicate map keys.

earandap
  • 1,446
  • 1
  • 13
  • 22
0

You can autowire context and get all the interested beans from it in a @PostConstruct method and create a hashmap with it.

Or

If you want that Map to be shared amongst multiple classes, make it a @Bean

@Configuration
class SomeConfig{

  @Autowire Context context;
  @Bean(name = "mapBean")
  public Map<String, MyCustomClassName1> mapBean() {
    Map<String, MyCustomClassName1> map = new HashMap<>();
    //populate the map here - from Context
    return map;
  }
}
so-random-dude
  • 15,277
  • 10
  • 68
  • 113
0

Spring fills List by your beans. After you can create map in postConstruct

@Component
public class A {

  @Autowired
  private List<I> list;

  @Autowired
  private Map<String, I> map;   

  @PostConstruct
  private void init(){
    map = list.stream()
            .collect(Collectors.toMap(I::getName, element->element);
  }
}
so-random-dude
  • 15,277
  • 10
  • 68
  • 113
serhii
  • 1
  • 1