¿Qué clases deben ser autoconectadas por Spring (cuándo usar la inyección de dependencia)?

32

He estado usando Dependency Injection en Spring durante algún tiempo y entiendo cómo funciona y cuáles son algunos de los pros y los contras de usarlo. Sin embargo, cuando estoy creando una nueva clase a menudo me pregunto: ¿Debería esta clase ser administrada por Spring IOC Container?

Y no quiero hablar sobre las diferencias entre la anotación @Autowired, la configuración XML, la inyección de setter, la inyección de constructor, etc. Mi pregunta es general.

Digamos que tenemos un servicio con un convertidor:

@Service
public class Service {

    @Autowired
    private Repository repository;

    @Autowired
    private Converter converter;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
}

@Component
public class Converter {

    public CarDto mapToDto(List<Car> cars) {
        return new ArrayList<CarDto>(); // do the mapping here
    }
}

Claramente, el convertidor no tiene dependencias, por lo que no es necesario que se conecte automáticamente. Pero para mí parece mejor como autowired. El código es más limpio y fácil de probar. Si escribo este código sin DI, el servicio se verá así:

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        Converter converter = new Converter();
        return converter.mapToDto(cars);
    }
}

Ahora es mucho más difícil probarlo. Además, se creará un nuevo convertidor para cada operación de conversión, aunque siempre esté en el mismo estado, lo que parece una sobrecarga.

Hay algunos patrones bien conocidos en Spring MVC: Controladores que usan Servicios y Servicios que usan Repositorios. Luego, si el repositorio está conectado automáticamente (que generalmente es), entonces el servicio también debe estar conectado automáticamente. Y esto está bastante claro. ¿Pero cuándo usamos la anotación @Component? Si tiene algunas clases de utilidad estáticas (como convertidores, mapeadores), ¿las conecta automáticamente?

¿Intentas hacer que todas las clases sean autoconectadas? Entonces, todas las dependencias de clase son fáciles de inyectar (una vez más, fácil de entender y fácil de probar). ¿O intenta conectar automáticamente solo cuando es absolutamente necesario?

Pasé algún tiempo buscando algunas reglas generales sobre cuándo usar el cableado automático, pero no pude encontrar ningún consejo específico. Por lo general, la gente habla sobre "¿usa DI? (Sí / no)" o "qué tipo de inyección de dependencia prefiere", que no responde a mi pregunta.

¡Le agradecería cualquier consejo sobre este tema!

Kacper86
fuente
3
+1 para esencialmente "¿Cuándo va demasiado lejos?"
Andy Hunt
Echa un vistazo a esta pregunta y este artículo
Laiv

Respuestas:

8

De acuerdo con el comentario de @ ericW, y solo quiero agregar recuerda que puedes usar inicializadores para mantener tu código compacto:

@Autowired
private Converter converter;

o

private Converter converter = new Converter();

o, si la clase realmente no tiene ningún estado

private static final Converter CONVERTER = new Converter();

Uno de los criterios clave para saber si Spring debe crear una instancia e inyectar un bean es, ¿es ese bean tan complicado que desea burlarse de él en las pruebas? Si es así, entonces inyectarlo. Por ejemplo, si el convertidor realiza un viaje de ida y vuelta a cualquier sistema externo, conviértalo en un componente. O si el valor de retorno tiene un gran árbol de decisión con docenas de posibles variaciones basadas en la entrada, entonces simulalo. Etcétera.

Ya ha hecho un buen trabajo al poner en marcha esa funcionalidad y encapsularla, por lo que ahora es solo una cuestión de si es lo suficientemente compleja como para ser considerada una "unidad" separada para las pruebas.

Robar
fuente
5

No creo que deba @Autowired todas sus clases, debería depender del uso real, para sus escenarios debería ser mejor utilizar el método estático en lugar de @Autowired. No he visto ningún beneficio en el uso de @Autowired para esas clases de utilidades simples, y eso aumentará absolutamente el costo del contenedor Spring si no lo usa correctamente.

ericW
fuente
2

Mi regla general se basa en algo que ya dijiste: comprobabilidad. Pregúntese " ¿Puedo probar la unidad fácilmente? ". Si la respuesta es sí, entonces, en ausencia de cualquier otra razón, estaría de acuerdo. Por lo tanto, si desarrolla la prueba unitaria al mismo tiempo que está desarrollando, ahorrará mucho dolor.

El único problema potencial es que si el convertidor falla, su prueba de servicio también fallará. Algunas personas dirían que debes burlarte de los resultados del convertidor en pruebas unitarias. De esta forma, podrá identificar los errores más rápido. Pero tiene un precio: debe burlarse de todos los resultados de los convertidores cuando el convertidor real podría haber hecho el trabajo.

También supongo que no hay ninguna razón para usar diferentes convertidores dto.

Borjab
fuente
0

TL; DR: un enfoque híbrido de cableado automático para DI, y el reenvío del constructor para DI puede simplificar el código que presentó.

He estado buscando preguntas similares debido a cierta lógica web con errores / complicaciones de inicio de Spring Framework que involucran dependencias de inicialización de bean @autowired. He comenzado a mezclar otro enfoque DI: reenvío de constructor. Requiere condiciones como las que usted presenta ("Claramente, el convertidor no tiene dependencias, por lo que no es necesario que se conecte automáticamente"). Sin embargo, me gusta mucho la flexibilidad y sigue siendo bastante sencillo.

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        Converter converter = new Converter();
        return getAllCars(converter);
    }
}

o incluso como un rif de la respuesta de Rob

@Service
public class Service {

    @Autowired
    private Repository repository;

    private final Converter converter = new Converter(); // static if safe for that

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        return getAllCars(converter);
    }
}

Puede que no requiera un cambio de interfaz pública, pero lo haría. Sigue siendo el

public List<CarDto> getAllCars(Converter converter) { ... }

podría hacerse protegido o privado para reducir el alcance solo con fines de prueba / extensión.

El punto clave es que la DI es opcional. Se proporciona un valor predeterminado, que puede anularse de manera directa. Tiene su debilidad a medida que aumenta el número de campos, pero para 1, quizás 2 campos, preferiría esto en las siguientes condiciones:

  • Muy pequeño número de campos.
  • El valor predeterminado casi siempre se usa (DI es una costura de prueba) o el valor predeterminado casi nunca se usa (detener espacio) porque me gusta la coherencia, por lo que esto es parte de mi árbol de decisión, no una verdadera restricción de diseño

Último punto (un poco OT, pero relacionado con cómo decidimos qué / dónde @autowired): la clase de convertidor, tal como se presenta, es un método de utilidad (sin campos, sin constructor, podría ser estático). ¿Tal vez la solución sería tener algún tipo de método mapToDto () en la clase Cars? Es decir, empuje la inyección de conversión a la definición de Cars, donde probablemente ya esté íntimamente vinculada:

@Service
public class Service {

   @Autowired
   private Repository repository;

   public List<CarDto> getAllCars() {
    return repository.findAll().stream.map(c -> c.mapToDto()).collect(Collectors.toList()));
   }
}
Kristian H
fuente
-1

Creo que un buen ejemplo de esto es algo como SecurityUtils o UserUtils. Con cualquier aplicación Spring que administre usuarios, necesitará alguna clase de Util con un montón de métodos estáticos como:

getCurrentUser ()

isAuthenticated ()

isCurrentUserInRole (autoridad de autoridad)

etc.

y nunca he autoconectado estos. JHipster (que uso como un buen juez de mejores prácticas) no lo hace.

Janome314
fuente
-2

Podemos separar las clases en función de la función de esa clase de modo que los controladores, el servicio, el repositorio, la entidad. Podemos usar otras clases para nuestra lógica solo no con el propósito de controladores, servicio, etc., en esa ocasión podríamos anotar esas clases con @component. Esto registrará automáticamente esa clase en el contenedor de resorte. Si está registrado, será administrado por contenedor de resorte. El concepto de inyección de dependencia e inversión de control se puede suministrar a esa clase anotada.

SathishSakthi
fuente
3
esta publicación es bastante difícil de leer (muro de texto). ¿Te importaría editarlo en una mejor forma?
mosquito