Some subtleties of injection & # 039; e collections in Spring & # 039; e

Hello! My name is Vladislav Rodin. I am currently teaching courses on software architecture and high load software architecture on the OTUS portal. Now in OTUS’e open a set for a new course stream Spring Framework Developer. In anticipation of the start of the course, I decided to write a little copyright material that I want to share with you.


Background

Spring contains a lot of “magic” within itself, carrying out some unobvious things on its own. Ignorance or misunderstanding of this can lead to side effects that you may encounter when writing your application using this framework.

One of these unobvious things is the injection of Java Collection Framework interfaces. Having independently stepped on a rake related to this topic, and having heard the next questions from my colleagues, I decided to sort it out and record the results of my research in the form of an article with the hope that it would help someone already in work or during the initial development of Spring.

Scenario

Let’s look at a service that will work with movie heroes. There will be 3 of them: Rambo, the Terminator and Gandalf. Each of them will represent a separate class, and their parent will be common – Hero.

public class Hero {
}

@Component
public class Rambo extends Hero {

    @Override
    public String toString() {
        return "Rambo";
    }
}

@Component
public class Terminator extends Hero {

    @Override
    public String toString() {
        return "Terminator";
    }
}


@Component
public class Gandalf extends Hero {
    @Override
    public String toString() {
        return "Gandalf";
    }
}

Injection List

Let’s assume that we want to create a service that will work with the heroes of the militants. Probably, you will have to inject a list of such heroes into it.

Not a problem! Create a service and configuration:

@Service
@Getter
public class ActionHeroesService {
    @Autowired
    List actionHeroes;
}

@Configuration
public class HeroesConfig {

    @Bean
    public List action() {
        List result = new ArrayList<>();
        result.add(new Terminator());
        result.add(new Rambo());
        return result;
    }
}

Everything is fine, but when checking, you can find that Gandalf is on the list too!

Spring, seeing that it was necessary to inject the List, bypassed the beans located in the context, found among them all that fit the generic type, collected List from them and injected it, ignoring our List.

How to get Spring to do what we want?

Option 1. Crutch

Since the problem is precisely in trying to inject the Java Collection Framework’s interface, you can simply replace List with ArrayList in the service and, of course, in the configuration so that Spring finds an instance of the class we need. Then everything will work as we expected.

@Configuration
public class HeroesConfig {

    @Bean
    public ArrayList action() {
        ArrayList result = new ArrayList<>();
        result.add(new Terminator());
        result.add(new Rambo());
        return result;
    }
}

@Service
@Getter
public class ActionHeroesService {
    @Autowired
    ArrayList actionHeroes;
}

Option 2. Correct

Another way to tie Spring’s hands is to ask him to inject into the service not just any List, but a bean with a special name, adding Qualifier. Thus, we will be able to inject our bean.

@Service
@Getter
public class ActionHeroesService {
    @Autowired
    @Qualifier("action")
    List actionHeroes;
}

Injection Maps

If many people know about the nuance of List injection injection, then things are generally worse with Map.
Let’s write a service that will work with the main characters of the films. Map will be injected into it, containing by name the names of the films, and by the values ​​of the beans of the main characters:

@Service
@Getter
public class MainCharactersService {
    @Autowired
    Map mainCharactersByFilmName;
}

@Configuration
public class HeroesConfig {

    @Bean
    public Map mainCharactersByFilmName() {
        Map result = new HashMap<>();

        result.put("rambo", new Rambo());
        result.put("terminator", new Terminator());
        result.put("LOTR", new Gandalf());

        return result;
    }
}

At startup, you can see that Gandalf’s key is not LOTR, but gandalf, from which we can conclude that it was not the name of the film that was written, but the name of the bean, whereas in the case of Rimbaud and the terminator it was just lucky: the names of the main characters coincide with movie titles.

In fact, when you need to inject a Map, the key of which is String, and the value of the beans, Spring (as in the case of the List) will simply ignore the Map we proposed, go through the context and collect all the matching beans s, and will create a Map with them as values ​​and with their names as keys.

The workarounds are similar to those that worked for the List:

Option 1. Crutch

Replace Map with HashMap:

@Service
@Getter
public class MainCharactersService {
    @Autowired
    HashMap mainCharactersByFilmName;
}


@Configuration
public class HeroesConfig {

    @Bean
    public HashMap mainCharactersByFilmName() {
        HashMap result = new HashMap<>();

        result.put("rambo", new Rambo());
        result.put("terminator", new Terminator());
        result.put("LOTR", new Gandalf());

        return result;
    }
}

Option 2. Correct

Add Qualifier:

@Service
@Getter
public class MainCharactersService {
    @Autowired
    @Qualifier("mainCharactersByFilmName")
    Map mainCharactersByFilmName;
}

Similar Posts

Leave a Reply