Algumas sutilezas das coleções de injeção no Spring

Olá a todos! Meu nome é Vladislav Rodin. Atualmente, estou ministrando cursos sobre arquitetura de software e arquitetura de software de alta carga no portal OTUS. Agora, no OTUS, existe um conjunto aberto para o novo segmento do curso Developer no Spring Framework . Antecipando o início do curso, decidi escrever um pouco de material com direitos autorais que quero compartilhar com você.





fundo


A primavera contém muita "mágica" em si mesma, realizando algumas coisas não óbvias por conta própria. A ignorância ou incompreensão disso pode levar a efeitos colaterais que você pode encontrar no processo de escrever seu aplicativo usando essa estrutura.

Uma dessas coisas não óbvias é a injeção de interfaces do Java Collection Framework. Tendo pisado de forma independente em um ancinho relacionado a este tópico, e tendo ouvido as próximas perguntas de meus colegas, decidi resolvê-lo e registrar os resultados de minha pesquisa na forma de um artigo com a esperança de que isso ajudaria alguém que já estivesse no trabalho ou durante o desenvolvimento inicial do Spring.

Cenário


Vejamos um serviço que funcionará com heróis de filmes. Haverá três deles: Rambo, Terminator e Gandalf. Cada um deles representará uma classe separada, e seus pais serão comuns - Herói.

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";
    }
}

Lista de Injeção


Vamos supor que queremos criar um serviço que funcione com os heróis dos militantes. Provavelmente, você terá que injetar uma lista de tais heróis nela.

Sem problemas! Crie um serviço e configuração:

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

@Configuration
public class HeroesConfig {

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

Está tudo bem, mas, ao verificar, pode-se descobrir que Gandalf está na lista!

Spring, como era necessário injetar a Lista, ignorou os feijões localizados no contexto, encontrou entre eles todos que se encaixavam no tipo genérico, coletou a Lista deles e a injetou, ignorando nossa Lista.

Como fazer com que o Spring faça o que queremos?

Opção 1. Muleta


Como o problema está justamente na tentativa de injetar a interface do Java Collection Framework, você pode simplesmente substituir List por ArrayList no serviço e, é claro, na configuração para que o Spring encontre uma instância da classe de que precisamos. Então tudo funcionará como esperávamos.

@Configuration
public class HeroesConfig {

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

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

Opção 2. Mais correta


Outra maneira de amarrar as mãos de Spring é pedir que ele injete no serviço não apenas em qualquer Lista, mas em um bean com um nome especial, adicionando Qualificador. Assim, seremos capazes de injetar nosso feijão.

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

Mapas de injeção


Se muitas pessoas sabem sobre as nuances da injeção de List, as coisas geralmente são piores com o Mapa.
Vamos escrever um serviço que funcione com os personagens principais dos filmes. O mapa será injetado nele, contendo os nomes dos filmes pelas teclas e pelos valores dos feijões dos personagens principais:

@Service
@Getter
public class MainCharactersService {
    @Autowired
    Map<String, Hero> mainCharactersByFilmName;
}

@Configuration
public class HeroesConfig {

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

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

        return result;
    }
}

Na inicialização, você pode ver que a chave de Gandalf não é LOTR, mas gandalf, a partir da qual podemos concluir que não era o nome do filme que foi escrito, mas o nome do bean, enquanto que no caso de Rimbaud e o terminador teve apenas sorte: os nomes dos personagens principais coincidem com títulos de filmes.

De fato, quando é necessário injetar um mapa, cuja chave é String e o valor dos beans, Spring (como no caso da lista) simplesmente ignorará o mapa que propusemos, passará pelo contexto e coletará todos os beans adequados se criará um mapa com eles como valores e com seus nomes como chaves.

As soluções alternativas são semelhantes às que funcionaram para a lista:

Opção 1. Muleta


Substitua o mapa pelo HashMap:

@Service
@Getter
public class MainCharactersService {
    @Autowired
    HashMap<String, Hero> mainCharactersByFilmName;
}


@Configuration
public class HeroesConfig {

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

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

        return result;
    }
}

Opção 2. Mais correta


Adicionar Qualificador:

@Service
@Getter
public class MainCharactersService {
    @Autowired
    @Qualifier("mainCharactersByFilmName")
    Map<String, Hero> mainCharactersByFilmName;
}

Source: https://habr.com/ru/post/undefined/


All Articles