Crear un bean según el contexto donde se inyecte con Spring

Escrito por picodotdev el .
java planeta-codigo
Enlace permanente Comentarios

Spring permite inyectar un bean construido utilizando información del contexto o clase donde se inyecte. Una caso de uso para esta funcionalidad es eliminar la habitual forma estática de inyectar las referencias a un Logger de una librería de logging, la inyección según el contexto permite proporciona el colaborador del Logger no como una variable estática sino en el constructor igual que otros colaboradores. Los beneficios son dos, se elimina una referencia estática que facilita los teses unitarios y se elimina un poco de código repetido en cada clase donde se use un Logger.

Spring

La opción habitual para inyectar una instancia de Logger con la que emitir trazas es hacerlo mediante un método estático y guardar la referencia en una propiedad estática. Esta es incluso la forma recomendada en el ejemplo de varias de las librerías de Logging. Cada clase donde se quiera añadir Logging requiere añadir el mismo código. Las propiedades estáticas son algo a evitar para evitar algunos problemas como la mayor dificultad de realizar pruebas unitarias.

En el artículo Opciones de arquitectura para emitir trazas en una aplicación Java mostraba varias opciones de como inyectar el Logger en la clase que quiera emitir trazas. Una la habitual de forma estática mostrada a continuación que es la que se suele mostrar como ejemplo de código en las propias librerías de Logging.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package io.github.picodotdev.blogbitix.springinjectionpoint;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public Service {

    private static final Logger logger = LogManager.getLogger(Service.class);

    public Service() {
    }

    public void method() {
        logger.info("Message");
    }
}
Service-1.java

Para evitar una referencia estática del Logger en la clase que dificulta las pruebas unitarias la opción es inyectar el Logger como un colaborador en el constructor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package io.github.picodotdev.blogbitix.springinjectionpoint;

import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

@Component
public class Service {

    private Logger logger;

    public Service(Logger logger) {
        this.logger = logger;
    }

    public void echo() {
        logger.info("Hello World!");
    }
}
Service-2.java

Una de las propiedades de la inversión de control es que no sean las clases las que creen sus dependencias sino que sea el contenedor el que las inyecte según las propiedades que necesita su constructor. Spring y Spring Boot proporcionan un contenedor de inversión de control con el que evitar la obtención de la referencia al Logger de forma estática y transformarlo a una inyección en el constructor siempre y cuando la clase en la que inyectar el Loggger esté gestionada por el contenedor de dependencias de Spring, de lo contrario hay que seguir recurriendo a la variable estática o gestionar manualmente la inyección de la dependencia al crear las instancias.

Crear un bean según el contexto donde se inyecte con Spring

Las instancias de las dependencias en Spring se crean en los métodos anotados con la anotación Bean pero en el caso del Logger el construir la instancia depende del contexto, la clase, donde se inyecte ya que para obtener la referencia del Logger depende de la clase.

El método de creación del bean puede recibir un InjectionPoint a partir del cual conocer cuál es la clase en la que inyecta el Logger y obtener una referencia a este según su clase que lo va a contener.

Como hay múltiples instancias de loggers según se inyecten hay que anotarlo también con la anotación @Scope(“prototype”) para que Spring crea una instancia cada vez que se solicita.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.github.picodotdev.blogbitix.springinjectionpoint;

import java.lang.reflect.Field;
import java.util.Optional;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.MethodParameter;

@Configuration
public class Beans {

    @Bean
    @Scope("prototype")
    Logger logger(InjectionPoint ip) {
        Optional<Class> clazzParameter = Optional.of(ip.getMethodParameter()).map(MethodParameter::getContainingClass);
        if (clazzParameter.isPresent()) {
            return LogManager.getLogger(clazzParameter.get());
        }
        Optional<Class> clazzClass = Optional.ofNullable(ip.getField()).map(Field::getDeclaringClass);
        if (clazzClass.isPresent()) {
            return LogManager.getLogger(clazzClass.get());
        }
        throw new IllegalArgumentException();
    }
}
Beans.java

Con esta funcionalidad de Spring es posible evitar los Logger estáticos e inyectarlos como un colaborador en el constructor. La funcionalidad de Logging funciona exactamente igual.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.github.picodotdev.blogbitix.springinjectionpoint;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class Main implements CommandLineRunner {

    @Autowired
    private Service service;

    @Override
    public void run(String... args) throws Exception {
        service.echo();
    }

    public static void main(String[] args) {
        new SpringApplicationBuilder(Main.class)
                .listeners(new Events())
                .application()
                .run();
    }
}
Main.java
1
2
2022-12-22 19:32:27,479 INFO  [main]  io.github.picodotdev.blogbitix.springinjectionpoint.Service Hello World!

System.out
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run


Comparte el artículo: