Archivo de la etiqueta: spring

Otros tipos de inyección de dependencias

No es la primera vez que hablo en este blog sobre inyección de dependencias, el patrón que nos permite pasar todas las dependencias que una clase necesita en el constructor en vez de como argumentos en métodos o utilizar clases estáticas.

En el caso de Java, donde últimamente paso la mayoría de mi tiempo, contamos con Spring y Guice como las maneras más conocidas de inyectar dependencias, veamos un ejemplo de Spring.

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ClassWithDependencies {
    private final DependentClass dependentInstance;

    public void doSomethingWithDependency(){
        dependentInstance.doSomething();
    }
}

@Component
public class DependentClass {
    public void doSomething(){
        System.out.println("Doing something")
    }
}

En este ejemplo, con las anotaciones @Component de Spring y @RequiredArgsConstructor de Lombok (un pre-procesador que nos permite agregar setters, getters y constructores a Java) podemos definir ClassWithDependencies como una clase que recibe DependentClass como parámetro del constructor, y luego operar sobre ella.

En artículos anteriores hemos hablado de otros sistemas como el de ASP.net vNext o incluso crear el nuestro propio. Creando un motor de inyección de dependencias con C#

Es importante destacar que en este ejemplo concreto, la inyección construye una nueva instancia de la clase cada vez que se inyecta, siendo la más sencilla de las opciones de inyección.

Una instancia por ejecución (Singleton)

Sin embargo, podemos personalizar aún más el tipo de inyección que hacemos. Volviendo a nuestro ejemplo anterior, supongamos que DependentClass contiene información de la máquina (memoria, procesador, etc) en la que se ejecuta, y por tanto no cambiará a lo largo de la vida de nuestra aplicación.

En este caso, una nueva instancia de cada clase sería innecesario o incluso costoso, dependiendo del tipo de inyección, para lo cual, en la definición del componente, especificamos qué tipo de inyección queremos

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ClassWithDependencies {
    private final MachineContext machineContext;

    public void doSomethingWithDependency(){
        machineContext.getProcessor();
    }
}

@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class MachineContext {
    public String getProcessor() {
        return "Intel";
    }
}

Comparemos esto con definir un patrón singleton de manera manual:

public class ClassWithDependencies {
    private final MachineContext dependentInstance;

    public void doSomethingWithDependency(){
        MachineContext.getInstance().getProcessor();
    }
}

public class MachineContext {
    private static MachineContext instance;

    public String getProcessor() {
        return "Intel";
    }

    private MachineContext() {
    }

    public static MachineContext getInstance(){
        if(instance == null){
            instance = new MachineContext();
        }
        return instance;
    }
}

La principal diferencia de usar Singleton en inyección respecto al singleton estándar que podemos ver en el ejemplo anterior es con inyección de dependencias tenemos las ventajas de singleton y mantenemos la capacidad de probar nuestro sistema, ya que en una prueba de ClassWithDependencies podríamos simular o utilizar un mock de MachineContext (por ejemplo si queremos probar cómo se comporta nuestra aplicación en distintos contextos).

Una instancia por petición (Request)

Una variante de Singleton especialmente para web, ocurre cuando el contexto depende de la petición o la sesión. Para este ejemplo, tenemos una función que se dedica a llevar un registro de las excepciones, y queremos, para cada excepción, registrar además el Id del usuario al que le ocurrió la excepción.

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GlobalExceptionHandler {
    private final Request request;

    public void handleException(Exception ex){
        logger.error(request.getUserId(), ex.getStacktrace())
        machineContext.getProcessor();
    }
}

@Component
@Getter
@Setter
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class Request {
    private String userId;
}

Este formato nos permite rellenar la clase Request en la capa del controlador de de nuestra aplicación, y en la otra directamente preguntar por el estado, sin necesidad de tener que pasar el objeto request a lo largo de toda la cadena de llamadas hasta la captura de esta excepción.

Con ello, evitamos que nuestras clases tengan una referencia al objeto únicamente por tener que pasarlo a la siguiente capa, se simplifica el número de parámetros que manejamos y podemos referirnos al contexto siempre que lo necesitemos.

Conclusiones

En este artículo hemos repasado inyección de dependencias de manera tradicional y además hemos visto las ventajas de utilizar construcciones como Singleton y Request.

Y tú, usas estas construcciones cuando desarrollas aplicaciones?

Anuncios