Inyección de campos desaconsejada “Field injection is not recommended” – Spring IOC
Introducción
Tras la ejecución de alguna herramienta de análisis estático de código o analizando el código desde algún IDE, es posible muy probable haberse topado con un mensaje en relación a los campos @Autowired
:
Field injection is not recommended
Esta publicación muestra los distintos tipos de inyección disponibles en Spring y cuáles son los patrones de uso recomendados para cada uno de ellos.
Tipos de Inyección
A pesar de que la documentación actual de Spring Framework (5.0.3) únicamente define dos tipos de inyección, en realidad hay tres:
- Inyección de dependencias basada en el constructor (Constructor-based)
- Inyección de dependencias basada en el setter (Setter-based)
- Inyección de dependencias basada en campos (Field-based)
Esta última es la que hace que las herramientas de análisis de código muestren advertencias, a pesar de ser una de las formas de inyección más comúnmente aceptadas y utilizadas.
Incluso se puede apreciar el uso de este tipo de inyección en alguno de los tutoriales de Spring a pesar de ser algo desaconsejado en la documentación:
Inyección de dependencias basada en el constructor (Constructor-based)
En la inyección de dependencias basada en el constructor, el constructor de la clase se anota con @Autowired
e incluye un número variable de parámetros con los objetos a inyectar.
1@Component
2public class ConstructorBasedInjection {
3
4 private final InjectedBean injectedBean;
5
6 @Autowired
7 public ConstructorBasedInjection(InjectedBean injectedBean) {
8 this.injectedBean = injectedBean;
9 }
10
11}
La principal ventaja de la inyección basada en el constructor es que puedes declarar los campos inyectados como finales ya que se iniciarán durante la instanciación de la clase. Esto es muy aconsejable para declarar las dependencias obligatorias.
Inyección de dependencias basada en el setter (Setter-based)
En la inyección de dependencias basada en setter, los métodos tipo setter se anotan con @Autowired
. El contenedor de Spring llamará a estos métodos una vez el Bean se haya instanciado para inyectar sus dependencias.
1@Component
2public class ConstructorBasedInjection {
3
4 private InjectedBean injectedBean;
5
6 @Autowired
7 public void setInjectedBean(InjectedBean injectedBean) {
8 this.injectedBean = injectedBean;
9 }
10
11}
Inyección de dependencias basada en campos (Field-based)
En la inyección de dependencias basada en campos, los propios campos de la clase se anotan con @Autowired
. El contenedor de Spring asignará el valor a estos campos una vez la clase se haya instanciado.
1@Component
2public class ConstructorBasedInjection {
3
4 @Autowired
5 private InjectedBean injectedBean;
6
7}
Como puede observarse, esta es la forma más limpia de inyectar las dependencias ya que nos evita añadir código extra para setters/getters y no es necesario declarar un constructor en la clase. El código se ve limpio y conciso, pero tal como el inspector de código nos advierte, este enfoque tiene algunos inconvenientes.
Desventajas de la inyección de dependencias basada en campos (Field-based)
Contraviene la declaración de campos inmutables
La inyección de dependencias basada en campos no funciona en variables que se declaran como finales/inmutables, ya que estos campos deberían de instanciarse al mismo tiempo que lo hace la clase que los contiene. La única forma de declarar dependencias finales/inmutables es a través de la inyección de dependencias basada en el constructor.
Facilita la transgresión/incumplimiento del principio de responsabilidad única
En programación orientada a objetos, el acrónimo SOLID define 5 principios de diseño que facilitarán el desarrollo de código comprensible, flexible y fácil de mantener.
La S en SOLID representa el principio de responsabilidad única, “Single responsibility principle” en inglés. Este principio recomienda que una clase debe ser responsable exclusivamente de una parte de la funcionalidad de la aplicación y dicha clase debe de encapsular esta responsabilidad en su totalidad, por lo que todos sus servicios deberían de estar relacionados estrechamente con esta misma funcionalidad.
La inyección basada en dependencias, facilita muchísimo la inyección y además lo hace de forma elegante, por lo que es muy fácil terminar con una clase con muchas dependencias sin que nada resulte sospechoso. Si en lugar de este tipo de inyección se hubiese elegido la inyección de dependencias basada en el constructor, a medida que se hubiesen añadido dependencias el constructor se habría hecho más y más grande y el código empezaría a apestar, enviando señales claras de que algo está mal.
Tener un constructor con más de diez parámetros es un claro síntoma de que la clase tiene demasiados colaboradores y de que probablemente es hora de comenzar a dividirla en partes mas pequeñas y fáciles de mantener.
Por tanto, aunque evidentemente, la inyección de dependencias basada en campos no es la responsable directa de romper el principio de responsabilidad única, ayuda a corromper el principio al ocultar señales que de otro modo se verían claramente.
Elevado acoplamiento con el contenedor encargado de la inyección de dependencias
El principal motivo para el uso de inyección de dependencias basada en campos es para evitar tener que añadir código adicional en el constructor de la clase o añadiendo getters y setters. En el fondo, esto implica que la única forma en la que a estos campos se les puede dar un valor es a través del contenedor de Spring y qué este los inyecte mediante reflexión. De otro modo, lo campos permanecerían con valor null y el objeto instanciado de esta clase estaría roto y sería inservible.
El patrón de diseño para la inyección de dependencias establece una separación entre la creación de las dependencias de una clase y la creación de la propia clase. El patrón asigna esta responsabilidad a un inyector de clases, permitiendo a la aplicación estar desacoplada y seguir el principio de responsabilidad única así como el de Inversión de la dependencia (SOLID de nuevo). Por lo tanto, al emplear @Autowire
para inyectar directamente sobre los campos, se pierde el desacoplamiento que se busca ya que en lugar de tener acoplada la clase ahora tenemos acoplado el inyector de clases (en este caso Spring), ya que sin este inyector concreto la clase no podría tener estos campos inyectados y por tanto sería inservible fuera del contenedor de Spring.
Esto significa que si queremos utilizar la clase fuera del contenedor de aplicaciones, por ejemplo durante los tests unitarios, nos vemos obligados a utilizar el contenedor de Spring para instanciar la clase, ya que no existe otro modo posible de dar valor a estos campos @Autowired
(salvo reflexión).
Dependencias ocultas
El empleo del patrón de inyección de dependencias implica que las clases afectadas muestren de forma pública estas dependencias de algún modo. Bien mediante su exposición en el constructor como parámetros cuando son obligatorias, o bien en algún método (setters) cuando son opcionales. Cuando se emplea la inyección de dependencias basada en campos, la clase está de forma inherente ocultando sus dependencias al mundo exterior.
Conclusión
Hemos visto las distintas desventajas de la inyección basada en campos y cómo esta debe de evitarse en la medida de lo posible a pesar de resultar elegante y sencilla. El procedimiento recomendado es el de emplear la inyección basada en el constructor cuando se trate de dependencias obligatorias permitiendo a estas declararlas como inmutables y previniendo que puedan tener un valor null. Y por otro lado emplear la inyección de dependencias basada en el setter cuando se trate de dependencias opcionales.
Comentarios en "Inyección de campos desaconsejada “Field injection is not recommended” – Spring IOC"
La teoria, es perfecta y me quedo super claro.
Concuerdo en todo, muchas gracias
Gracias!!
Para solventar eso he estado utilizando @Resoruce en vez de @Autowired ya que esta si es parte del core de Java y al menos en mis proyectos Spring Boot estas estan siendo correctamente inyectados por Spring. No se si esto tendrá alguna desventaja, pero de omento me ha servido.