[Pregunta de la entrevista] Spring IoC y DI, ensamblaje automático y dependencia circular

Spring IoC es el diseño más clásico de Spring, y el ensamblaje automático es una operación de configuración simplificada para la "automatización" de inyección de IoC. Aunque la inyección de IoC nos ayuda a administrar las dependencias entre objetos, aún puede ocurrir un diseño incorrecto y causar dependencias circulares. El poderoso Spring también ofrece algunas soluciones elegantes.

Tabla de contenido

Que es IoC

IoC e inversión de control

Cómo entender el contenedor de IoC

Método de inyección de IoC

Comparación de ventajas y desventajas

Montaje automático

Método de montaje

Dependencia circular

Solución


Que es IoC

El contenedor Spring IoC es responsable del ciclo de vida de los objetos y de la relación (de dependencia) entre los objetos.
Al crear un nuevo bean, el contenedor de IoC inyectará automáticamente otros beans de los que depende el nuevo bean, en lugar de crearlo manualmente usted mismo.
Escriba la descripción de la imagen aquí

  • El contenedor de IoC completa automáticamente la inicialización del objeto, evitando escribir una gran sección de código de inicialización durante el proceso de desarrollo.
  • No es necesario conocer los detalles al crear una instancia.

Ventajas: No
será muy intrusivo para el código comercial y el objeto tiene mejor capacidad de prueba, reutilización y escalabilidad.

Las ventajas parecen muy abstractas y pueden entenderse junto con ejemplos: consulte la sección 2.3 "El secreto de la primavera" de Wang Fuqiang. Valor agregado de IoC

IoC e inversión de control

El nombre completo de IoC es InversionofControl, que se traduce como "inversión de control".

  1. Quién controla quién : en el modelo de desarrollo tradicional, todos creamos objetos creando directamente un objeto, lo que significa que el objeto del que depende está controlado directamente por usted mismo, pero después de tener un contenedor IOC, es directamente por el contenedor IoC Al control. Entonces, "quién controla a quién" es, por supuesto, el objeto de control del contenedor de IoC.
  2. Qué controlar: objetos de control.
  3. Por qué es una reversión : cuando no hay IoC, todos creamos activamente objetos dependientes en nuestros propios objetos, esto es, una rotación hacia adelante. Pero con IoC, los objetos dependientes son creados directamente por el contenedor de IoC y luego inyectados en los objetos inyectados. Los objetos dependientes cambian de ser adquiridos activamente a aceptados pasivamente, por lo que es a la inversa.
  4. Qué aspectos se invierten : La adquisición de objetos dependientes se invierte.

Generalmente se cree (error, como se discutirá más adelante) que IoC también tiene un alias llamado DI (Inyección de dependencia), es decir, inyección de dependencia. La "inyección de dependencia" describe claramente "el objeto inyectado depende del objeto dependiente de la configuración del contenedor de IoC".
La clave para entender la DI es: "Quién depende de quién, por qué necesita depender, quién inyecta a quién y qué se inyecta", luego analicemos en profundidad:

  1. Quién depende de quién : El "objeto inyectado" depende del "objeto dependiente". Por ejemplo, el objeto A depende de B, entonces el contenedor de IoC necesita inyectar el objeto B antes de inyectar el objeto A; el objeto A depende del contenedor de IoC; el objeto B se inyecta en el objeto A, por lo que A es el objeto inyectado y B es dependiente Objeto, A depende de B.
  2. Por qué tenemos que confiar en : los objetos gestionados por contenedores necesitan contenedores de IoC para proporcionar los recursos externos que necesitan los objetos;
  3. Quién inyecta a quién : Obviamente, el contenedor de IoC inyecta un objeto, es decir, inyecta "objetos dependientes";
  4. Qué se inyecta: Es inyectar recursos externos (incluidos objetos, recursos, datos constantes) requeridos por un objeto.

La relación entre los dos
Inversión de control (Inversión de control) es una idea de diseño de código que se basa en el principio de inversión. El método específico utilizado es la denominada inyección de dependencia.

Cómo entender el contenedor de IoC

Cuando creamos manualmente una instancia de automóvil por nosotros mismos, comenzamos de nuevo desde abajo hacia arriba:
Escriba la descripción de la imagen aquí
el constructor de cada clase llama directamente al constructor del código subyacente. En este proceso, necesitamos entender cómo se define todo el constructor de la clase Car / Framework / Bottom / Tire para ser nuevo / inyectado paso a paso. Los cambios en la capa inferior afectarán a todos los cambios en la capa superior. Si cada vez que modificamos una clase, tenemos que modificar todas las clases que dependen de ella, y el costo de mantenimiento del software será demasiado alto.

Cuando IoC Container hace este trabajo, es al revés. Comienza desde el nivel superior y baja para encontrar las dependencias, y después de llegar al nivel inferior, pasa al siguiente paso (un poco como un recorrido de profundidad primero). La llamada inyección de dependencia consiste en pasar la clase inferior como parámetro a la clase alta para realizar el "control" de la clase alta a la clase baja.
Escriba la descripción de la imagen aquí
Aquí, el contenedor de IoC puede ocultar directamente los detalles de la creación de la instancia específica, en nuestra opinión es como una fábrica:
Escriba la descripción de la imagen aquí
somos como los clientes de la fábrica. Solo necesitamos solicitar una instancia de Car a la fábrica, y luego crea una instancia de Car para nosotros de acuerdo con Config. No nos importa cómo se crea paso a paso esta instancia de Car.

Método de inyección de IoC

  • Inyección de constructor
    La lista de parámetros del método constructor del objeto inyectado declara la lista de parámetros del objeto dependiente
private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;

@Autowired
public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) {
    this.dependencyA = dependencyA;
    this.dependencyB = dependencyB;
    this.dependencyC = dependencyC;
}

Después de Spring 4.3+, la inyección del constructor admite métodos de inyección sin pantalla.

private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;

// @Autowired
public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) {
    this.dependencyA = dependencyA;
    this.dependencyB = dependencyB;
    this.dependencyC = dependencyC;
}
  • Inyección de método de Setter La lista
    desetter()parámetrosdelmétodo delobjeto inyectadodeclara el objeto dependiente
private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;

@Autowired
public void setDependencyA(DependencyA dependencyA) {
    this.dependencyA = dependencyA;
}

@Autowired
public void setDependencyB(DependencyB dependencyB) {
    this.dependencyB = dependencyB;
}

@Autowired
public void setDependencyC(DependencyC dependencyC) {
    this.dependencyC = dependencyC;
}
  • La inyección de interfaz
    es engorrosa. Rara vez se ha utilizado.
    La lista de parámetros del método de interfaz implementado por el objeto inyectado declara el objeto dependiente
  • Inyección de campo
@Autowired
private DependencyA dependencyA;

@Autowired
private DependencyB dependencyB;

@Autowired
private DependencyC dependencyC;

Comparación de ventajas y desventajas

Método de inyección ventaja Desventaja Escena practica
Inyección de campo Simple, fácil de agregar nuevas dependencias Puede haber una falla en la inyección y una NullPointedException; no disponible en Test y otros módulos; no se puede usar para los campos finales, por lo que no se puede garantizar la invariancia de los campos. Spring Official no recomienda esta forma de escribir.
inyección de setter Alta flexibilidad, fácil de modificar objetos dependientes. Se requiere una verificación no nula para los objetos dependientes que solo usan inyección de setter; el objeto no puede ingresar al estado listo inmediatamente después de que se completa la construcción Vuelva a inyectar objetos dependientes con valores no predeterminados (inyección de constructor); para dependencias no esenciales, se recomienda la inyección de setter.
inyección de constructor Una vez construido el objeto, ha entrado en el estado listo y se puede utilizar de inmediato. Cuando hay muchos objetos dependientes, la lista de parámetros del método de construcción será más larga y será más difícil de mantener y utilizar De acuerdo con el principio de responsabilidad única, debe considerar la reconstrucción en este momento. El uso inadvertido también puede causar dependencias circulares.  

Después de Spring 4.x, el método de inyección debe ser inyección de constructor o de establecimiento según sea necesario.

¿Por qué el funcionario no recomienda la inyección de campo?

  1. Invasión de responsabilidad única
    Agregar dependencias es muy simple, quizás demasiado simple. Agregar seis, diez o incluso un montón de dependencias no es nada difícil, lo que dificulta la detección de programas que violan el principio de responsabilidad única.
    Cuando se utiliza el método de inyección del constructor, es relativamente fácil encontrar programas que violen el principio de responsabilidad única. Cuando se usa el método del constructor para inyectar, en cierto punto, los parámetros en el constructor se han vuelto tantos que es obvio que algo anda mal. Tener demasiadas dependencias generalmente significa que su clase tiene más responsabilidad. Obviamente viola el principio de responsabilidad única (SRP: principio de responsabilidad única).
  2. No se pueden declarar campos sin cambios.
    La inyección de campo no puede inyectar campos finales, solo la inyección de constructor puede inyectar campos finales
  3. Dependencias ocultas. El
    uso de contenedores de inyección de dependencias significa que las clases ya no son responsables de los objetos dependientes. La responsabilidad de obtener los objetos dependientes se elimina de la clase y el contenedor de IoC lo ayudará a ensamblarlo. Cuando una clase ya no es responsable de los objetos dependientes, debe usar métodos de interfaz pública o constructores con más claridad, de esta manera, puede comprender claramente qué necesita la clase y si es opcional (inyección de setter) u obligatoria. (Inyección de constructor).
  4. Acoplamiento estrecho del contenedor de inyección de dependencia
    Una de las ideas centrales del marco de inyección de dependencia es que las clases administradas por el contenedor no deben depender de los objetos dependientes utilizados por el contenedor. En otras palabras, esta clase debe ser un POJO (Objeto Java ordinario simple) que se puede instanciar por separado y también puede proporcionarle las dependencias que necesita . Solo de esta manera, puede crear una instancia de esta clase en la prueba unitaria sin tener que iniciar el contenedor de inyección de dependencia para lograr la separación de la prueba (iniciar el contenedor es más una prueba de integración).
    Sin embargo, cuando se usa la inyección directa variable, no hay forma de instanciar directamente esta clase y satisfacer todas sus dependencias. Esto significa que los objetos dependientes deben ser nuevos manualmente o solo se pueden usar en el alcance del contenedor de IoC.

Montaje automático

El autoensamblaje es una operación de configuración simplificada para inyectar dependencias en la "automatización".
Cuando la propiedad de un objeto es otro objeto, es necesario crear una instancia para la propiedad del objeto al crear una instancia. Esta es la asamblea.
Si un objeto solo expresa la dependencia a través de la interfaz, entonces esta dependencia se puede cambiar con una implementación específica diferente sin que el objeto lo sepa. Pero esto tendrá un problema, en la configuración tradicional de inyección de dependencias, tenemos que dejar claro qué referencia de bean ensamblar el atributo, una vez que hay muchos beans, es difícil de mantener. En base a este escenario, Spring usa anotaciones para el ensamblaje automático para resolver este problema. El cableado automático significa que el desarrollador no necesita saber qué referencia de frijol ensamblar, y el trabajo de identificación se realizará en primavera. Junto con el ensamblaje automático, existe la "detección automática", que reconoce automáticamente qué clases deben configurarse como beans para el ensamblaje. De esta manera, entendemos que el ensamblaje automático es una operación de configuración simplificada para "automatizar" la inyección de dependencias.

Método de montaje

  • ByName consiste en ensamblar el bean con el mismo nombre que el atributo.
  • ByType es ensamblar beans del mismo tipo que los atributos. - Constructor consiste en ensamblar beans del mismo tipo y parámetros a través del constructor.
  • Autodetect es una combinación de constructor y byType. Primero se ejecutará el constructor y, si falla, se realizará byType.

Qué método de ensamblaje elegir, debe configurar el atributo autowire de la etiqueta. Si no está configurado, el valor predeterminado es byName type, que se ensambla automáticamente de acuerdo con el nombre del atributo. Los más utilizados anteriormente son byName y byType. Durante el ensamblaje automático, el bean ensamblado debe ser el único que coincida con los atributos, ni más ni menos, y solo un bean que se pueda ensamblar se puede ensamblar automáticamente con éxito. De lo contrario, se lanzará una excepción. Si desea unificar el tipo de ensamblaje automático de todos los beans, puede configurar el atributo default-autowire en la etiqueta. Por supuesto, si el atributo autowire está configurado, aún podemos ensamblar manualmente los atributos, y el ensamblaje manual anulará el ensamblaje automático.

Después de la primavera 2.5, se proporciona el ensamblaje automático de anotaciones . Pero para usar estas anotaciones, debe configurarlas en el archivo de configuración <context:annotation-config />. Solo con esta configuración puede usar anotaciones para el ensamblaje automático, y el ensamblaje basado en anotaciones está deshabilitado de forma predeterminada.
Las anotaciones de autoensamblaje más utilizadas son las siguientes: @Autowired, @Qualifier (@Resource, @Inject, @Named son estándares JavaEE y no se recomiendan).
La anotación @Autowired es del tipo byType . Esta anotación se puede utilizar en propiedades, establecedores y constructores. Al usar esta anotación, no es necesario agregar un método de establecimiento para la propiedad en la clase. Pero este atributo es obligatorio, es decir, debe estar ensamblado, si no se puede ensamblar ningún bean adecuado, se lanzará una excepción: `NoSuchBeanDefinitionException, si se requiere = falso, no se lanzará ninguna excepción. Otra situación es que hay varios beans del mismo tipo al mismo tiempo y también se lanzará esta excepción. En este momento, es necesario aclarar más qué Bean se ensamblará. En este momento, la anotación @Qualifier se puede combinar con el valor del nombre del Bean. La anotación @Qualifier usa byName para el ensamblaje , de modo que es posible especificar qué bean con el mismo nombre se usa para ensamblar entre varios beans del mismo tipo. La anotación @Qualifier juega un papel en la reducción del alcance de los beans candidatos de autoensamblaje.

La detección automática de la configuración también es la característica más poderosa de springmvc. Siempre que sea una configuración <context:component-scan base-package="">o anotación @ComponentScan("")
, el atributo de paquete base especifica el paquete que se detectará y analizará automáticamente.
Esta configuración escaneará automáticamente las clases marcadas con anotaciones de estereotipo en el paquete especificado y sus subpaquetes, y registrará estas clases como spring beans, de modo que no es necesario configurarlas como etiquetas de bean una por una en el archivo de configuración. Las anotaciones de estereotipo incluyen: @Controller, @Components, @Service, @Repository y anotaciones personalizadas marcadas con @Component. El ID del bean generado toma por defecto el nombre no calificado de la clase, es decir, la primera letra del nombre de la clase se cambia a minúsculas. Puede escribir el valor de la identificación del bean en el valor de estas anotaciones, como @Controller ("helloworld"). Si desea refinar el alcance del paquete que se escanea, puede usar <context:include-filter>y <context:exclude-filter>. El método de uso específico no se describirá en detalle aquí. Tenga en cuenta que las clases que no se escanean no se pueden registrar como beans y no se pueden utilizar para ensamblar otras clases. Por tanto, el alcance del paquete base de esta configuración es muy importante.

Dependencia circular

Si no presta atención a la inyección de dependencias, habrá dependencias circulares:
Orden de dependencia de Bean: BeanA -> BeanB -> BeanA
Por ejemplo:

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}

Ejecute SpringBootApplication:

@SpringBootApplication
@ComponentScan("com.example.circulardependency.constructor")
public class CirculardependencyApplication {

    public static void main(String[] args) {
        SpringApplication.run(CirculardependencyApplication.class, args);
    }
}

Se informarán los siguientes errores:

BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA': Requested bean is currently in creation: Is there an unresolvable circular reference?

También dibujará un círculo de dependencia:
Escriba la descripción de la imagen aquí

Solución

La dependencia circular se produce debido a problemas de diseño, y la mejor manera de abordarla es rediseñar .

https://zhuanlan.zhihu.com/p/84267654

En el desarrollo real, a menudo no se permite el vuelco, por lo que existen varios remedios.

1. Cambiar a inyección de incubadora (recomendado)

A diferencia de la inyección del constructor, se setterinyecta bajo demanda y permite que el objeto dependiente sea nulo ;

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

Agregar prueba unitaria:

@RunWith(SpringRunner.class)
@SpringBootTest
@ComponentScan("com.example.circulardependency.setter")
public class CirculardependencyApplicationTests {
    @Bean
    public CircularDependencyB getDependencyB() {
        return new CircularDependencyB();
    }

    @Bean
    public CircularDependencyA getDependencyA() {
        CircularDependencyA circularDependencyA = new CircularDependencyA();
        circularDependencyA.setCircularDependencyB(getDependencyB());
        return circularDependencyA;
    }

    @Test
    public void contextLoads() {
        System.out.println("Hello world.");
        CircularDependencyA circularDependencyA = getDependencyA();
        System.out.println(circularDependencyA.getCircularDependencyB().getMessage());
    }
}

El uso de anotaciones de campo también puede resolver dependencias circulares, pero las anotaciones de campo son prácticas recomendadas no oficiales , por lo que no se dan ejemplos aquí.

2. @ anotación perezosa

@LazyInicialización retrasada. En este ejemplo, CircularDependencyA se construirá primero y luego CircularDependencyB se construirá para romper el círculo de dependencia.

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}

3. 使用 ApplicationContextAware, InitializingBean

ApplicationContextAware obtiene SpringContext, que se utiliza para cargar el bean; InitializingBean define la acción después de establecer la propiedad del bean.

@Component
public class CircularDependencyA implements InitializingBean, ApplicationContextAware {
    private CircularDependencyB circB;
    private ApplicationContext context;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        this.circB = context.getBean(CircularDependencyB.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    public CircularDependencyB getCircularDependencyB() {
        return circB;
    }
}

Principio : Está relacionado con el ciclo de vida de Spring Bean: primero instancia, y luego llama a setApplicationContext (), afterPropertiesSet (); Entonces, después de que CircularDependencyB se haya cargado, cargue CircularDependencyA.
Si primero se crea una instancia de CircularDependencyA, y cuando se llama a afterPropertiesSet (), se encuentra que no se ha cargado CircularDependencyB. Primero, se carga CircularDependencyB (se llama al constructor, porque se ha creado una instancia de CircularDependencyA en este momento, por lo que se puede cargar sin problemas), se establece la propiedad circB y se carga CircularDependencyA.

Original: https://blog.csdn.net/programmer_at/article/details/82389221
[1] Método de inyección de IoC:  https://www.vojtechruzicka.com/field-dependency-injection-considered-harmful/
[2] Loop Dependencia:  https://www.baeldung.com/circular-dependencies-in-spring
[3] Inyección de campo:  https://blog.marcnuri.com/field-injection-is-not-recommended/
[4] IoC y DI: Versión de explicación precisa del concepto: http://sishuok.com/forum/blogPost/list/2427.html Versión
fácil de entender: https://www.zhihu.com/question/23277575/answer/169698662

● La optimización de rendimiento de Tomcat8 más sólida de la historia

¿Por qué Alibaba puede resistir 10 mil millones en 90 segundos? - La evolución de la arquitectura distribuida de alta concurrencia del lado del servidor

Plataforma de comercio electrónico B2B: función de pago electrónico ChinaPay UnionPay

Aprenda el candado distribuido de Zookeeper, deje que los entrevistadores lo miren con admiración

Solución de bloqueo distribuido de Redisson con microservicio de pico de comercio electrónico de SpringCloud

Vea más artículos buenos, ingrese a la cuenta oficial, por favor, excelente en el pasado

Una cuenta pública profunda y conmovedora 0.0

Supongo que te gusta

Origin blog.csdn.net/a1036645146/article/details/109504106
Recomendado
Clasificación