¿Entiendes el contenedor padre-hijo en Spring?

En el proyecto SSM, el contenedor Spring es el contenedor principal y SpringMVC es el contenedor secundario. El contenedor secundario puede acceder a los beans del contenedor principal, pero el contenedor principal no puede acceder a los beans del contenedor secundario.

Un paso más cerca, algunos amigos también pueden saber que parece que es posible simplemente usar un contenedor SpringMVC sin un contenedor padre-hijo, y el proyecto también puede ejecutarse.

Entonces, ahora surge la pregunta: dado que un solo contenedor SpringMVC puede hacer que el proyecto se ejecute, ¿por qué usamos contenedores padre-hijo? ¿Cuáles son las ventajas de los contenedores padre-hijo?

1. Contenedor padre-hijo

En primer lugar, de hecho, el diseño padre-hijo es muy común. Después de usar el contenedor padre-hijo, si va al contenedor padre para encontrar el bean, simplemente busque el bean en el contenedor padre; contenedor secundario, regrese si lo encuentra, continúe buscando en el contenedor principal si no lo encuentra, hasta que lo encuentre (si ha buscado en todos los contenedores principales y aún no tiene nada, entonces solo puede lanzar una excepción).

2. ¿Por qué necesitamos contenedores padre-hijo?

2.1 Presentación del problema

¿Por qué necesita contenedores padre-hijo? ¿No podemos simplemente usar un contenedor honestamente?

Dado que hay contenedores padre-hijo en el contenedor Spring, esta cosa debe tener sus escenarios de uso.

Supongamos que tengo un proyecto de varios módulos, que incluye un módulo de comerciante y un módulo de cliente. Tanto el módulo de comerciante como el módulo de cliente tienen administración de roles RoleService. La estructura del proyecto es la siguiente:

├── admin
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   └── resources
├── consumer
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── org
│       │   │       └── admin4j
│       │   │           └── consumer
│       │   │               └── RoleService.java
│       │   └── resources
│       │       └── consumer_beans.xml
├── merchant
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── org
│       │   │       └── admin4j
│       │   │           └── merchant
│       │   │               └── RoleService.java
│       │   └── resources
│       │       └── merchant_beans.xml
└── pom.xml

Ahora hay una clase RoleService tanto en el consumidor como en el comerciante, y luego registre esta clase con el contenedor Spring en sus respectivos archivos de configuración.

org.admin4j.consumer.RoleService:

public class RoleService {
    public String hello() {
        return "hello consumer";
    }
}

org.admin4j.merchant.RoleService:

public class RoleService {
    public String hello() {
        return "hello merchant";
    }
}

consumidor_beans.xml es el siguiente:

<?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 class="org.admin4j.consumer.RoleService" id="roleService"/>
</beans>

mercantil_beans.xml es el siguiente:

<?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 class="org.admin4j.merchant.RoleService" id="roleService"/>
</beans>

Tenga en cuenta que estos dos frijoles tienen el mismo nombre.

Ahora, en el módulo de administración, tanto el consumidor como el comerciante son dependientes, y estos dos archivos de configuración se cargan al mismo tiempo, entonces, ¿se pueden registrar dos beans con el mismo nombre de diferentes módulos en el contenedor de Spring al mismo tiempo?

el código se muestra a continuación:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");
ctx.refresh();
org.admin4j.merchant.RoleService rs1 = ctx.getBean(org.admin4j.merchant.RoleService.class);
org.admin4j.consumer.RoleService rs2 = ctx.getBean(org.admin4j.consumer.RoleService.class);

Después de esta ejecución, se lanzarán las siguientes preguntas:

imagen

Los amigos pueden ver que este org.admin4j.consumer.RoleServiceservicio no se puede encontrar, pero el otro RoleService sí se encuentra porque, de forma predeterminada, el bean con el mismo nombre definido más adelante anula al anterior, por lo que si hay un bean, no se puede encontrar.

Si no se permite la cobertura de bean, se puede realizar la siguiente configuración:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocations("consumer_beans.xml", "merchant_beans.xml");
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();

En este momento, se informa un error tan pronto como comienza:

imagen

El significado también es relativamente claro, la definición de Bean entra en conflicto, por lo que la definición falla.

Entonces, ¿hay alguna forma de resolver el problema anterior de manera elegante? ¡La respuesta es el contenedor padre-hijo!

2.2 Contenedores padre-hijo

Para el problema anterior, podemos configurar el consumidor y el comerciante como una relación padre-hijo o una relación hermano, lo que puede resolver este problema muy bien.

2.2.1 Hermandad

Primero mire la relación entre hermanos, el código es el siguiente:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ClassPathXmlApplicationContext child1 = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child2 = new ClassPathXmlApplicationContext("merchant_beans.xml");
child1.setParent(ctx);
child2.setParent(ctx);
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();
org.admin4j.consumer.RoleService rs1 = child1.getBean(org.admin4j.consumer.RoleService.class);
org.admin4j.merchant.RoleService rs2 = child2.getBean(org.admin4j.merchant.RoleService.class);
System.out.println("rs1.hello() = " + rs1.hello());
System.out.println("rs2.hello() = " + rs2.hello());

Amigos, echen un vistazo, este tipo de contenedor se crea para el consumidor y el comerciante respectivamente. Este tipo de relación de contenedor es contenedor hermano. Los dos hermanos tienen un padre común que es ctx. Ahora puede obtener su propio Bean en cada contenedor.

Cabe señalar que en la estructura anterior, el contenedor secundario puede obtener el bean del padre, pero no puede obtener el bean del contenedor hermano, es decir, si el consumidor hace referencia al bean en el comerciante, entonces hay un problema con la configuración anterior.

2.2.2 Relación padre-hijo

Ahora suponga que el consumidor se usa como contenedor principal y el comerciante se usa como contenedor secundario, entonces la configuración es la siguiente:

ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext("merchant_beans.xml");
child.setParent(parent);
child.refresh();
org.admin4j.consumer.RoleService rs1 = parent.getBean(org.admin4j.consumer.RoleService.class);
org.admin4j.merchant.RoleService rs2 = child.getBean(org.admin4j.merchant.RoleService.class);
org.admin4j.consumer.RoleService rs3 = child.getBean(org.admin4j.consumer.RoleService.class);
System.out.println("rs1.hello() = " + rs1.hello());
System.out.println("rs2.hello() = " + rs2.hello());
System.out.println("rs3.hello() = " + rs3.hello());

Primero cree dos contenedores, a saber, padre e hijo, y luego configure el padre para el contenedor hijo, recuerde actualizar el contenedor hijo después de completar la configuración.

Ahora podemos obtener el Bean original en el contenedor padre del contenedor padre, u obtener el Bean original del contenedor hijo o el Bean del padre del contenedor hijo.

Este es el contenedor padre-hijo.

El contenedor principal y el contenedor secundario son esencialmente dos contenedores diferentes aislados entre sí, por lo que se permite que existan beans con el mismo nombre. Cuando el contenedor secundario llama al método getBean para obtener un Bean, si el contenedor actual no lo encuentra, irá al contenedor principal para buscar y seguirá buscando hasta que lo encuentre.

El núcleo es BeanFactory. BeanFactory tiene una subclase HierarchicalBeanFactory. El nombre es BeanFactory con una relación jerárquica:

public interface HierarchicalBeanFactory extends BeanFactory {

 /**
  * Return the parent bean factory, or {@code null} if there is none.
  */
 @Nullable
 BeanFactory getParentBeanFactory();

 /**
  * Return whether the local bean factory contains a bean of the given name,
  * ignoring beans defined in ancestor contexts.
  * <p>This is an alternative to {@code containsBean}, ignoring a bean
  * of the given name from an ancestor bean factory.
  * @param name the name of the bean to query
  * @return whether a bean with the given name is defined in the local factory
  * @see BeanFactory#containsBean
  */
 boolean containsLocalBean(String name);

}

La relación padre-hijo se puede configurar siempre que sea una subclase de HierarchicalBeanFactory. El diagrama de relación padre-hijo es el siguiente:

imagen

2.3 Circunstancias especiales

Cabe señalar que no todos los métodos para obtener beans admiten la búsqueda de relaciones padre-hijo, y algunos métodos solo se pueden buscar en el contenedor actual y no buscarán en el contenedor principal:

ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext("merchant_beans.xml");
child.setParent(parent);
child.refresh();
String[] names1 = child.getBeanNamesForType(org.admin4j.merchant.RoleService.class);
String[] names2 = child.getBeanNamesForType(org.admin4j.consumer.RoleService.class);
System.out.println("names1 = " + Arrays.toString(names1));
System.out.println("names2 = " + Arrays.toString(names2));

Como antes, cuando buscamos el nombre del bean según el tipo, usamos el método getBeanNamesForType, proporcionado por la interfaz ListableBeanFactory, y esta interfaz no tiene una relación de herencia con la interfaz HierarchicalBeanFactory, por lo que el método getBeanNamesForType no admite la búsqueda de beans. en el contenedor principal, que solo busca frijoles en el contenedor actual.

¡pero! Si realmente tiene una necesidad y desea poder encontrar el nombre del Bean de acuerdo con el tipo, y también poder ir automáticamente al contenedor principal para encontrarlo, entonces puede usar las herramientas proporcionadas por Spring, de la siguiente manera:

ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("consumer_beans.xml");
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext();
child.setParent(parent);
child.refresh();
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(child, org.admin4j.consumer.RoleService.class);
for (String name : names) {
    System.out.println("name = " + name);
}

Sin embargo, esta búsqueda no puede encontrar el nombre del Bean con el mismo nombre en el contenedor padre-hijo.

2.4 Spring 和 SpringMVC

El contenido anterior se entiende, y la relación entre Spring y SpringMVC es fácil de entender. Spring es el contenedor principal y SpringMVC es el contenedor secundario.

En SpringMVC, cuando se inicializa DispatcherServlet, se crea un contenedor SpringMVC y se establece el padre para el contenedor SpringMVC. El código relevante es el siguiente:

FrameworkServlet#initWebApplicationContext:

protected WebApplicationContext initWebApplicationContext() {
 WebApplicationContext rootContext =
   WebApplicationContextUtils.getWebApplicationContext(getServletContext());
 WebApplicationContext wac = null;
 if (this.webApplicationContext != null) {
  // A context instance was injected at construction time -> use it
  wac = this.webApplicationContext;
  if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
   // The context has not yet been refreshed -> provide services such as
   // setting the parent context, setting the application context id, etc
   if (cwac.getParent() == null) {
    // The context instance was injected without an explicit parent -> set
    // the root application context (if any; may be null) as the parent
    cwac.setParent(rootContext);
   }
   configureAndRefreshWebApplicationContext(cwac);
  }
 }
 if (wac == null) {
  // No context instance was injected at construction time -> see if one
  // has been registered in the servlet context. If one exists, it is assumed
  // that the parent context (if any) has already been set and that the
  // user has performed any initialization such as setting the context id
  wac = findWebApplicationContext();
 }
 if (wac == null) {
  // No context instance is defined for this servlet -> create a local one
  wac = createWebApplicationContext(rootContext);
 }
 return wac;
}

El rootContext aquí es el contenedor principal, y wac es el contenedor secundario. No importa de qué manera obtenga el contenedor secundario, intentará establecer un contenedor principal para él.

Si no configuramos el contenedor Spring por separado en un proyecto web, sino que configuramos directamente el contenedor SpringMVC y luego escaneamos todos los beans en el contenedor SpringMVC, no hay problema para hacerlo y el proyecto puede ejecutarse normalmente. Sin embargo, en proyectos generales, seguimos separando estos dos contenedores, lo que tiene las siguientes ventajas:

  1. Administración conveniente, SpringMVC se ocupa principalmente de los beans relacionados con la capa de control, como el controlador, el analizador de vistas, el procesador de parámetros, etc., mientras que la capa Spring controla principalmente los beans relacionados con la capa comercial, como el servicio, el mapeador, fuente de datos, transacción , autoridad, etc. Frijoles relacionados.
  2. Para los novatos, configurar los dos contenedores por separado puede comprender mejor la relación entre las capas de controlador, servicio y Dao, y también puede evitar escribir códigos ridículos que inyectan controladores en la capa de servicio.

3. Resumen

Ok, ahora todos deberían entender el contenedor padre-hijo en el contenedor Spring, ¿verdad? Puede configurar un contenedor principal para contenedores que no sean ListableBeanFactory.El contenedor principal no puede acceder a los beans del contenedor secundario, pero el contenedor secundario puede acceder a los beans del contenedor principal.

3.1 Características de los contenedores padre-hijo

  • El contenedor principal y el contenedor secundario están aislados entre sí, y dentro de ellos pueden existir beans con el mismo nombre.

  • El contenedor secundario puede acceder a los beans en el contenedor principal, pero el contenedor principal no puede acceder a los beans en el contenedor secundario

  • Al llamar al método getBean del subcontenedor para obtener el bean, comenzará a buscar el contenedor por encima del contenedor actual hasta encontrar el bean correspondiente.

  • El bean en el contenedor principal se puede inyectar en el contenedor secundario a través de cualquier método de inyección, pero el bean en el contenedor secundario no se puede inyectar en el contenedor principal debido al segundo punto

  • La interfaz BeanFactory admite la búsqueda jerárquica, pero la interfaz ListableBeanFactory no admite la búsqueda jerárquica.

  • La clase de herramientas BeanFactoryUtils proporciona algunos métodos muy prácticos, como los métodos que admiten la búsqueda de jerarquías de beans, etc.

3.2 Mire hacia atrás al problema del contenedor padre-hijo springmvc

Pregunta 1: ¿Es posible usar solo un contenedor en springmvc?

Funciona bien con un solo contenedor.

Pregunta 2: Entonces, ¿por qué necesita usar contenedores padre-hijo en springmvc?

Por lo general, cuando usamos springmvc, adoptamos una estructura de 3 capas, capa de controlador, capa de servicio y capa de dao; el contenedor principal contendrá la capa de dao y la capa de servicio, mientras que el contenedor secundario contiene solo la capa de controlador; estos dos contenedores forman la relación de contenedor padre-hijo, la capa del controlador generalmente inyecta beans en la capa de servicio.

El uso de contenedores padre-hijo puede evitar que algunas personas inyecten beans en la capa del controlador en la capa de servicio, lo que genera confusión en toda la jerarquía de dependencia.

Las necesidades del contenedor principal y del contenedor secundario también son diferentes. Por ejemplo, el contenedor principal debe tener soporte para transacciones, y se inyectarán algunos componentes de extensión que admiten transacciones, pero el controlador en el contenedor secundario no los usa en absoluto. , y no le importan estos. En el contenedor secundario Necesito inyectar beans relacionados con springmvc, y estos beans no se usarán en el contenedor principal, y no les importan algunas cosas. Separe estas cosas que no les importan unos de otros, lo que puede evitar algunos errores innecesarios, y los contenedores padre-hijo también se cargarán un poco más rápido.

Supongo que te gusta

Origin blog.csdn.net/agonie201218/article/details/131681028
Recomendado
Clasificación