Tipos de inyección de dependencias con Spring

Introducción

La inyección de dependencias es un patrón de desarrollo de software donde los objetos no son responsables de inicializar sus dependencias, sino que estas son provistas a través de otro objeto. En el caso de Spring ese objeto es el contenedor IoC el cual es provisto por los módulos spring-core y spring-beans.
En este articulo, mostraremos los diferentes tipos de inyección de beans que disponemos con Spring.

Dependencias

Para usar la funcionalidades básicas del contenedor y la inyección de dependencias necesitamos agregar las siguientes dependencias a nuestro proyecto maven:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.1.RELEASE</version>
</dependency>

Las versiones más recientes las encontramos en Maven Central
Si adicionalmente queremos usar las anotaciones del estándar JSR-330 debemos agregar esta también:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

Tipos de inyección de dependencias

Las variantes de DI soportadas por el contenedor IoC de Spring son constructor, setter y field.

1. Constructor

En este caso el contenedor se encarga de invocar el constructor de la clase pasando los argumentos como dependencias. Es la recomendada para la mayoría de los casos, puedes leer mas detalles en este post de Oliver Gierke (Spring Data) o en la documentacion de Spring.

2. Setter

Aquí el contenedor asigna las dependencias usando los métodos setter de los atributos. Recomendada para dependencias opcionales.

3. Field

En este caso no se requiere un método para la asignación de la dependencia sino que esta se asigna a través del API de reflexión. Se usa para casos muy básicos y generalmente no recomendada.

Configuración

Para configurar la inyección de dependencias tenemos dos opciones, usar configuración XML y JavaConfig con anotaciones de Spring.

1. XML

Para hacer esto partiremos del archivo application-context.xml que tenemos en la carpeta resources de nuestra aplicación. Vamos a definir dos beans, un dataSource con propiedades de conexión a una base de datos, las cuales inicializaremos a través del Contenedor IoC y un dataBaseService que tomara el dataSource.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dataSource" class="ve.com.proitcsolution.service.DataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
	<property name="url" value="jdbc:mysql://127.0.0.1/" />
	<property name="username" value="username" />
	<property name="password" value="password" />
    </bean>
    <bean id="dataBaseService" class="ve.com.proitcsolution.service.DatabaseServiceWithoutAnnotations">
        <constructor-arg ref="dataSource" />
    </bean>
</beans>

La inyección de los atributos del dataSource son inicializadas a través de setter-inyección con la etiqueta property, la inyección del dataSource a través del constructor en la clase dataBaseService se hace con la etiqueta constructor-arg.

La inyección de tipo field no es compatible con la configuración XML y requiere el uso de anotaciones.

 

2. JavaConfig

La configuración usando JavaConfig es mucho menos verbosa y solo requiere el uso de anotaciones especificas del framework.

  • @Configuration: Para marcar que esta clase va inicializar beans
  • @ComponentScan: Para buscar beans con anotaciones Spring
  • @Bean: En métodos que inicializan bean

En ejemplo lo podemos ver en la siguiente clase:

@Configuration
@ComponentScan(basePackages = {"ve.com.proitcsolution.service"})
public class JavaConfig {

  @Bean
  public DataSource dataSource() {
    DataSource dataSource = new DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://127.0.0.1/");
    dataSource.setUsername("production username");
    dataSource.setPassword("production password");
    return dataSource;
  }

  @Bean
  public AuditService auditService() {
    AuditService auditService = new AuditService("INFO");
    return auditService;
  }
}

Uso

Una vez configurados nuestros beans podemos usarlos a través de anotaciones de Spring y anotaciones del JSR-330

1 Anotaciones de Spring

El framework provee anotaciones especificas para declaración de beans como @Component, @Service, @Repository y para inyección @Autowire, las primeras se usan sobre los tipos y la última en los puntos de inyección.
En el siguiente ejemplo las clase DatabaseService inicializa la dependencia dataSource a través del constructor y la dependencia auditService a través del setter:

@Service
public class DatabaseService {

  private DataSource dataSource;
  private AuditService auditService;

  @Autowired
  public DatabaseService(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  @Autowired
  public void setAuditService(AuditService auditService) {
    this.auditService = auditService;
  }
}

2 Anotaciones estándar JSR 330

A través del JSR 330 “Dependency Injection for Java” se estandarizaron las anotaciones para DI, y entre las disponibles tenemos @Inject para inyección de componentes y @Named para declaración de beans.
Si bien @Inject y @Autowire son equivalentes pero @Autowire provee funcionales adicionales a las del estandar. Y lo mismo pasa para @Named y @Component.
En el siguiente test se ve un ejemplo del uso de las anotación @Inject, en este caso el tipo de inyección que se usa es field.

@SpringJUnitConfig(JavaConfig.class)
public class DependencyInjectionJavaConfigTest {

  @Inject
  DatabaseService databaseService;
  @Inject
  DataSource dataSource;
  @Inject
  AuditService auditService;
  //Tests...
}

Conclusión

En este artículo vimos un ejemplo de como usar las tres tipos de inyección de dependencias con Spring, sus características y las opciones de configuración disponibles.
El código completo esta disponible en Github.