¿Qué es exactamente Field Injection y cómo evitarlo?

130

Leí en algunas publicaciones sobre Spring MVC y Portlets que no se recomienda la inyección de campo . Según tengo entendido, la inyección de campo es cuando se inyecta un Bean @Autowiredasí:

@Component
public class MyComponent {
    @Autowired
    private Cart cart;
}

Durante mi investigación también leí sobre la inyección de constructores :

@Component
public class MyComponent {
    private final Cart cart;

    @Autowired
    public MyComponent(Cart cart){
       this.cart = cart;
    }
}

¿Cuáles son las ventajas y las desventajas de ambos tipos de inyecciones?


EDITAR 1: Como esta pregunta está marcada como un duplicado de esta pregunta, la revisé. Porque no hay ejemplos de código ni en la pregunta ni en las respuestas, no me queda claro si estoy en lo cierto con mi suposición de qué tipo de inyección estoy usando.

T. Jung
fuente
3
Si la inyección de campo es tan mala como lo describe, ¿por qué Spring lo permite? La inyección de campo tiene sus propios beneficios, al hacer que el código sea más legible y menos detallado. Si es lo suficientemente disciplinado en su codificación, puede estar seguro de que las cosas no se romperán incluso si usa la inyección de campo.
cenizas
@ashes Porque era una característica interesante en ese momento y las implicaciones no se pensaron completamente. La misma razón que Date(int,int,int)existe.
chrylis -cautelosoptimista-

Respuestas:

225

Tipos de inyecciones

Hay tres opciones para inyectar dependencias en un bean:

  1. A través de un constructor
  2. A través de setters u otros métodos
  3. A través de la reflexión, directamente en los campos

Está utilizando la opción 3. Eso es lo que sucede cuando utiliza @Autowireddirectamente en su campo.


Pautas de inyección

Una pauta general recomendada por Spring (consulte las secciones sobre DI basada en Constructor o DI basada en Setter ) es la siguiente:

  • Para dependencias obligatorias o cuando se apunta a la inmutabilidad, use la inyección del constructor
  • Para dependencias opcionales o cambiables, use la inyección de setter
  • Evite la inyección de campo en la mayoría de los casos

Inconvenientes de la inyección de campo

Las razones por las que la inyección de campo está mal vista son las siguientes:

  • No puede crear objetos inmutables, como puede hacerlo con la inyección del constructor
  • Sus clases tienen un acoplamiento estrecho con su contenedor DI y no se pueden usar fuera de él
  • No se pueden crear instancias de sus clases (por ejemplo, en pruebas unitarias) sin reflexión. Necesita el contenedor DI para instanciarlos, lo que hace que sus pruebas se parezcan más a pruebas de integración
  • Sus dependencias reales están ocultas desde el exterior y no se reflejan en su interfaz (ni constructores ni métodos)
  • Es realmente fácil tener como diez dependencias. Si estuviera usando inyección de constructor, tendría un constructor con diez argumentos, lo que indicaría que algo está sospechoso. Pero puede agregar campos inyectados usando la inyección de campo indefinidamente. Tener demasiadas dependencias es una señal de alerta de que la clase generalmente hace más de una cosa y que puede violar el principio de responsabilidad única.

Conclusión

Dependiendo de sus necesidades, debe usar principalmente inyección de constructor o alguna combinación de inyección de constructor y setter. La inyección de campo tiene muchos inconvenientes y debe evitarse. La única ventaja de la inyección de campo es que es más conveniente escribir, lo que no supera todas las desventajas.


Otras lecturas

Escribí un artículo de blog sobre por qué la inyección de campo generalmente no se recomienda: Inyección de dependencia de campo considerada dañina .

Vojtech Ruzicka
fuente
12
En general, no es una buena idea ni agradable decirle al mundo que "se debe evitar la inyección de campo". Mostrar pros y contras y dejar que el otro decida uno mismo;) Mucha gente tiene otras experiencias y una forma propia de ver las cosas.
persona que hace dieta
6
Ese puede ser el caso aquí, pero hay otros casos en los que la comunidad ha llegado a un consenso general para desalentar algo. Tomemos, por ejemplo, la notación húngara.
Jannik
Usted da algunos buenos puntos como capacidad de prueba y visibilidad de dependencia, pero no estoy de acuerdo con todos. ¿La inyección de constructor no tiene inconvenientes? Puede ser deseable disponer de 5 o 6 campos para inyectar en clase que realicen composiciones reales de llamada. También estoy en desacuerdo contigo con la inmutabilidad. Tener campos finales no es obligatorio para tener una clase inmutable. Es preferible. Que es muy diferente.
davidxxx
Creo que quisiste decir "Para dependencias obligatorias o cuando se busca la inmutabilidad "
Alex Terreaux
1
Me estaba refiriendo al enlace al principio de la respuesta que enlaza con los documentos de primavera
Vojtech Ruzicka
47

Esta es una de las discusiones interminables en el desarrollo de software, pero las principales personas influyentes en la industria se están volviendo más obstinadas sobre el tema y comenzaron a sugerir la inyección de constructores como la mejor opción.

Inyección de constructor

Pros:

  • Mejor capacidad de prueba . No necesita ninguna biblioteca burlona o un contexto Spring en las pruebas unitarias. Puede crear un objeto que desee probar con la nueva palabra clave. Estas pruebas son siempre más rápidas porque no dependen del mecanismo de reflexión. ( Esta pregunta se hizo 30 minutos después. Si el autor hubiera utilizado la inyección del constructor, no habría aparecido).
  • Inmutabilidad . Una vez que se establecen las dependencias, no se pueden cambiar.
  • Código más seguro . Después de la ejecución de un constructor, su objeto está listo para usar, ya que puede validar cualquier cosa que se haya pasado como parámetro. El objeto puede estar listo o no, no hay un estado intermedio. Con la inyección de campo se introduce un paso intermedio cuando el objeto es frágil.
  • Expresión más limpia de dependencias obligatorias . La inyección de campo es ambigua en este asunto.
  • Hace que los desarrolladores piensen en el diseño . dit escribió sobre un constructor con 8 parámetros, que en realidad es el signo de un mal diseño y el antipatrón del objeto Dios . No importa si una clase tiene 8 dependencias en su constructor o en campos, siempre es incorrecto. La gente es más reacia a agregar más dependencias a un constructor que a través de campos. Funciona como una señal para tu cerebro de que debes detenerte un rato y pensar en la estructura de tu código.

Contras:

  • Más código (pero los IDE modernos alivian el dolor).

Básicamente, la inyección de campo es lo contrario.

Daniel Olszewski
fuente
1
testabilidad, sí, fue una pesadilla para mí burlarme de los frijoles inyectados de campo. Una vez, usé la inyección de contsructor, no necesito hacer ninguna burla innecesaria
Kenobiwan
25

Cuestion de gusto. Es tu decisión.

Pero puedo explicar por qué nunca uso la inyección del constructor .

  1. No quiero poner en práctica un constructor para todos mis @Service, @Repositoryy @Controllerfrijoles. Quiero decir, hay alrededor de 40-50 frijoles o más. Cada vez que agrego un nuevo campo, tendría que extender el constructor. No. No lo quiero y no tengo que hacerlo.

  2. ¿Qué pasa si su Bean (Servicio o Controlador) requiere que se inyecten muchos otros granos? Un constructor con 4+ parámetros es muy feo.

  3. Si estoy usando CDI, el constructor no me concierne.


EDITAR # 1 : Vojtech Ruzicka dijo:

la clase tiene demasiadas dependencias y probablemente está violando el principio de responsabilidad única y debería refactorizarse

Si. Teoría y realidad. Aquí hay un ejemplo: DashboardControllermapeado a una ruta única *:8080/dashboard.

My DashboardControllerrecopila mucha información de otros servicios para mostrarla en una página de descripción general del panel / sistema. Necesito este único controlador. Así que tengo que asegurar solo esta ruta (autenticación básica o filtro de rol de usuario).

EDICIÓN # 2 : Dado que todos están enfocados en los 8 parámetros en el constructor ... Este fue un ejemplo del mundo real: un código heredado de un cliente. He cambiado eso. La misma argumentación se aplica a mí para más de 4 parámetros.

Se trata de inyección de código, no de construcción de instancias.

dieter
fuente
34
Un constructor muy feo con 8 dependencias es realmente impresionante, ya que es una señal de alerta de que algo está mal, la clase tiene demasiadas dependencias y probablemente está violando el principio de responsabilidad única y debería refactorizarse. De hecho, es algo bueno.
Vojtech Ruzicka
6
@VojtechRuzicka seguro que no es agradable pero a veces no puedes evitarlo.
persona que hace dieta
4
Yo diría que una regla de 3, y mucho menos 40-50, las dependencias para cualquier clase deberían ser una señal de que necesita refactorizar. No hay forma de que una clase con 40 dependencias se apegue al director de responsabilidad única o al director de apertura / cierre.
Amin J
4
@AminJ La regla es genial, pero la realidad es diferente. La empresa en la que trabajo tiene más de 20 años y tenemos mucho código heredado. Refactorizar es una buena idea, pero cuesta dinero. Además no sé por qué se están diciendo, pero no era mi intención 40-50 dependencias, me refiero a 40-50 frijoles, componentes, módulos ...
Dieter
7
@dit, su situación es claramente una en la que la deuda técnica le está haciendo tomar decisiones subóptimas. Según sus propias palabras, se encuentra en una situación en la que su toma de decisiones está significativamente influenciada por el código heredado de más de 20 años. Al comenzar un nuevo proyecto, ¿recomendaría la inyección de campo en lugar de la inyección de constructor? Tal vez debería hacer una advertencia en su respuesta para indicar en qué casos elegiría la inyección de campo.
Umar Farooq Khawaja
0

Un comentario más: Vojtech Ruzicka declaró que Spring inyecta frijoles de tres maneras (la respuesta con la mayor cantidad de puntos):

  1. A través de un constructor
  2. A través de setters u otros métodos
  3. A través de la reflexión, directamente en los campos

Esta respuesta es INCORRECTA, porque PARA CADA TIPO DE INYECCIÓN, EL RESORTE UTILIZA REFLEXIÓN. Use IDE, establezca un punto de interrupción en el setter / constructor y verifique.

Esto puede ser una cuestión de gustos pero también puede ser una cuestión de CASO. @dieter proporcionó un caso excelente cuando la inyección de campo es mejor. Si está utilizando la inyección de campo en las pruebas de integración que están configurando el contexto de Spring, el argumento con la capacidad de prueba de la clase también es inválido, a menos que desee escribir más adelante en las pruebas de Sus pruebas de integración;)

wujek.oczko
fuente