Algunas sutilezas de las colecciones de inyección en primavera

¡Hola a todos! Me llamo Vladislav Rodin. Actualmente estoy impartiendo cursos sobre arquitectura de software y arquitectura de software de alta carga en el portal OTUS. Ahora en OTUS hay un conjunto abierto para el nuevo hilo del curso de Desarrollador en Spring Framework . En previsión del inicio del curso, decidí escribir un pequeño material de copyright que quiero compartir con ustedes.





Antecedentes


Spring contiene mucha "magia" dentro de sí misma, llevando a cabo algunas cosas no obvias por sí misma. La ignorancia o la incomprensión de esto pueden provocar efectos secundarios que puede encontrar en el proceso de escribir su aplicación usando este marco.

Una de estas cosas obvias es la inyección de interfaces de Java Collection Framework. Habiendo pisado de manera independiente un rastrillo relacionado con este tema y escuchando las siguientes preguntas de mis colegas, decidí resolverlo y registrar los resultados de mi investigación en forma de artículo con la esperanza de que ayudaría a alguien que ya está trabajando o durante el desarrollo inicial de Spring.

Guión


Veamos un servicio que funcionará con los héroes del cine. Habrá 3 de ellos: Rambo, Terminator y Gandalf. Cada uno de ellos representará una clase separada, y sus padres serán comunes: Héroe.

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 inyecciones


Supongamos que queremos crear un servicio que funcione con los héroes de los militantes. Probablemente, tendrás que inyectarle una lista de esos héroes.

¡No hay problema! Crear un servicio y configuración:

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

¡Todo está bien, pero al verificarlo se puede encontrar que Gandalf está en la lista!

Spring, al ver que era necesario inyectar la Lista, omitió los granos ubicados en el contexto, encontró entre ellos todos los que se ajustaban al tipo genérico, recolectó la Lista de ellos y la inyectó, ignorando nuestra Lista.

¿Cómo lograr que Spring haga lo que queremos?

Opción 1. Muleta


Dado que el problema es precisamente al tratar de inyectar la interfaz de Java Collection Framework, simplemente puede reemplazar List con ArrayList en el servicio y, por supuesto, en la configuración para que Spring encuentre una instancia de la clase que necesitamos. Entonces todo funcionará como esperábamos.

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

Opción 2. Más correcta


Otra forma de atar las manos de Spring es pedirle que inyecte en el servicio no solo cualquier Lista, sino un bean con un nombre especial, agregando Qualifier. Por lo tanto, podremos inyectar nuestro frijol.

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

Mapas de inyección


Si muchas personas conocen el matiz de la inyección de List, las cosas generalmente empeoran con el Mapa.
Escribamos un servicio que funcione con los personajes principales de las películas. Se inyectará un mapa en él, que contiene los nombres de las películas mediante las teclas y los valores de los beans de los personajes principales:

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

Al inicio, puede ver que la clave de Gandalf no es LOTR, sino gandalf, de lo que podemos concluir que no fue el nombre de la película que se escribió, sino el nombre del bean, mientras que en el caso de Rimbaud y el terminador fue una suerte: los nombres de los personajes principales coinciden con títulos de películas

De hecho, cuando es necesario inyectar un Mapa, cuya clave es String, y el valor de los beans, Spring (como en el caso de la Lista) simplemente ignorará el Mapa que propusimos, revisará el contexto y recogerá todos los beans adecuados. s, y creará un Mapa con ellos como valores y con sus nombres como claves.

Las soluciones alternativas son similares a las que funcionaron para la Lista:

Opción 1. Muleta


Reemplazar mapa con 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;
    }
}

Opción 2. Más correcta


Añadir calificador:

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

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


All Articles