Dependencies And Configuration In Detail
Dependencias y configuración en detalle
⚠️ Importancia baja: toda esta sección trata sobre configuración XML.
Como se mencionó en la sección anterior, puedes definir propiedades de beans y argumentos de constructores como referencias a otros beans administrados (colaboradores) o como valores definidos en línea. Los metadatos de configuración basados en XML de Spring admiten distintos subelementos dentro de los elementos <property/> y <constructor-arg/> para este propósito.
Valores directos (primitivos, cadenas, etc.)
El atributo value del elemento <property/> especifica una propiedad o un argumento de constructor como una representación de cadena legible para las personas. El servicio de conversión de Spring se utiliza para convertir estos valores desde String al tipo real de la propiedad o del argumento. El siguiente ejemplo muestra distintos valores establecidos:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- da lugar a una llamada a setDriverClassName(String) -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
El siguiente ejemplo utiliza el espacio de nombres p para una configuración XML todavía más concisa:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
El XML anterior es más conciso. Sin embargo, los errores tipográficos se detectan en tiempo de ejecución, no en tiempo de diseño, salvo que uses un IDE que admita el autocompletado de propiedades al crear definiciones de beans. Se recomienda encarecidamente contar con esta ayuda del IDE.
También puedes configurar una instancia de java.util.Properties de la siguiente manera:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- se tipa como java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
El contenedor de Spring convierte el texto dentro del elemento <value/> en una instancia de java.util.Properties mediante el mecanismo PropertyEditor de JavaBeans. Este es un atajo útil y uno de los pocos casos en los que el equipo de Spring prefiere usar el elemento anidado <value/> en lugar del atributo value.
El elemento idref
El elemento idref es una forma segura frente a errores de pasar el id —un valor de cadena, no una referencia— de otro bean del contenedor a un elemento <constructor-arg/> o <property/>. El siguiente ejemplo muestra cómo usarlo:
<bean id="collaborator" class="..." />
<bean id="client" class="...">
<property name="targetName">
<idref bean="collaborator" />
</property>
</bean>
El fragmento anterior es exactamente equivalente, en tiempo de ejecución, al siguiente:
<bean id="collaborator" class="..." />
<bean id="client" class="...">
<property name="targetName" value="collaborator" />
</bean>
La primera forma es preferible, porque al usar la etiqueta idref el contenedor puede validar, en el momento del despliegue, que el bean nombrado al que se hace referencia existe realmente. En la segunda variante, no se valida el valor que se pasa a la propiedad targetName del bean client. Por ello, los errores tipográficos solo se descubren, probablemente con consecuencias fatales, cuando se instancia el bean client.
Si client es un bean de ámbito prototype, el error tipográfico y la excepción resultante podrían descubrirse mucho después de desplegar el contenedor.
Un uso habitual —al menos en versiones anteriores a Spring 2.0— del elemento <idref/> es la configuración de interceptores de AOP en una definición de bean ProxyFactoryBean. Usar elementos <idref/> al especificar nombres de interceptores evita escribir incorrectamente el ID de un interceptor.
Referencias a otros beans (colaboradores)
El elemento ref es el elemento final dentro de una definición <constructor-arg/> o <property/>. Con él se establece el valor de una propiedad concreta de un bean como referencia a otro bean —un colaborador— administrado por el contenedor. El bean referenciado es una dependencia del bean cuya propiedad se va a establecer y se inicializa bajo demanda, según sea necesario, antes de establecer la propiedad.
Si el colaborador es un bean singleton, puede que el contenedor ya lo haya inicializado. En última instancia, todas las referencias apuntan a otro objeto. El ámbito y la validación dependen de si especificas el ID o el nombre del otro objeto mediante el atributo bean o parent.
Especificar el bean de destino mediante el atributo bean de la etiqueta <ref/> es la forma más general y permite crear una referencia a cualquier bean del mismo contenedor o de un contenedor padre, independientemente de que esté o no en el mismo archivo XML. El valor del atributo bean puede coincidir con el atributo id del bean de destino o con uno de los valores del atributo name de dicho bean. El siguiente ejemplo muestra cómo utilizar un elemento ref:
<ref bean="someBean"/>
Especificar el bean de destino mediante el atributo parent crea una referencia a un bean que está en un contenedor padre del contenedor actual. El valor del atributo parent puede coincidir con el atributo id del bean de destino o con uno de los valores de su atributo name. El bean de destino debe estar en un contenedor padre del actual.
Esta variante de referencia debe usarse principalmente cuando tienes una jerarquía de contenedores y quieres envolver, con un proxy que tenga el mismo nombre, un bean existente en un contenedor padre. El siguiente par de ejemplos muestra cómo utilizar el atributo parent:
<!-- en el contexto padre -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- inserta aquí las dependencias necesarias -->
</bean>
<!-- en el contexto hijo; el nombre del bean es el mismo que el del bean padre -->
<bean id="accountService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- observa cómo se referencia el bean padre -->
</property>
<!-- inserta aquí la configuración y las dependencias necesarias -->
</bean>
El atributo
localdel elementorefya no es compatible con el XSD de beans 4.0, ya que dejó de aportar ventajas respecto de una referenciabeannormal. Al actualizar al esquema 4.0, cambia las referenciasref localexistentes porref bean.
Beans internos
Un elemento <bean/> dentro de los elementos <property/> o <constructor-arg/> define un bean interno, como muestra el siguiente ejemplo:
<bean id="outer" class="...">
<!-- en lugar de usar una referencia a un bean objetivo, defínelo directamente en línea -->
<property name="target">
<bean class="com.example.Person"> <!-- este es el bean interno -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
Una definición de bean interno no requiere un ID ni un nombre. Si se especifica alguno, el contenedor no lo utiliza como identificador. El contenedor también ignora el indicador scope durante la creación, porque los beans internos siempre son anónimos y siempre se crean junto con el bean externo. No es posible acceder a los beans internos de forma independiente ni inyectarlos en beans colaboradores distintos del bean que los contiene.
Como caso excepcional, es posible recibir callbacks de destrucción desde un ámbito personalizado; por ejemplo, para un bean interno de ámbito de solicitud contenido en un bean singleton. La creación de la instancia del bean interno está vinculada al bean que la contiene, pero los callbacks de destrucción le permiten participar en el ciclo de vida del ámbito de solicitud. No es un escenario frecuente. Normalmente, los beans internos comparten el ámbito del bean que los contiene.
Colecciones
Los elementos <list/>, <set/>, <map/> y <props/> establecen propiedades y argumentos de los tipos de colección Java List, Set, Map y Properties, respectivamente. El siguiente ejemplo muestra cómo usarlos:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- da lugar a una llamada a setAdminEmails(java.util.Properties) -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- da lugar a una llamada a setSomeList(java.util.List) -->
<property name="someList">
<list>
<value>un elemento de lista seguido de una referencia</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- da lugar a una llamada a setSomeMap(java.util.Map) -->
<property name="someMap">
<map>
<entry key="una entrada" value="solo una cadena"/>
<entry key="una referencia" value-ref="myDataSource"/>
</map>
</property>
<!-- da lugar a una llamada a setSomeSet(java.util.Set) -->
<property name="someSet">
<set>
<value>solo una cadena</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
El valor de una clave o de un valor de mapa, así como un valor de conjunto, también puede ser cualquiera de los siguientes elementos:
bean | ref | idref | list | set | map | props | value | null
Mezcla de colecciones
El contenedor de Spring también admite mezclar colecciones. Puedes definir un elemento padre <list/>, <map/>, <set/> o <props/> y hacer que elementos hijos <list/>, <map/>, <set/> o <props/> hereden y sobrescriban valores de la colección padre. Es decir, los valores de la colección hija son el resultado de combinar los elementos de las colecciones padre e hija, y los elementos de la colección hija sobrescriben los valores especificados en la colección padre.
Esta sección sobre mezclas usa el mecanismo de beans padre e hijo. Si no estás familiarizado con las definiciones de beans padre e hijo, consulta primero la sección correspondiente.
El siguiente ejemplo muestra una mezcla de colecciones:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- la mezcla se especifica en la definición de la colección hija -->
<props merge="true">
<prop key="sales">sales@example.org</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
</beans>
Observa el uso del atributo merge="true" en el elemento <props/> de la propiedad adminEmails de la definición del bean child. Cuando el contenedor resuelve e instancia el bean child, la instancia resultante tiene una colección Properties llamada adminEmails que contiene la mezcla de la colección adminEmails hija con la del padre. El resultado es el siguiente:
administrator=administrator@example.org
sales=sales@example.org
support=support@example.co.uk
El conjunto de valores de la colección Properties hija hereda todos los elementos de propiedad del <props/> padre, y el valor hijo de support sobrescribe el de la colección padre.
Este comportamiento de mezcla se aplica de forma similar a los tipos de colección <list/>, <map/> y <set/>. En el caso concreto del elemento <list/>, se mantienen las reglas propias de List, es decir, que es una colección ordenada: los valores del padre preceden a todos los valores de la lista hija. En los tipos de colección Map, Set y Properties, no existe ordenación. Por tanto, las implementaciones internas de esos tipos de colección que usa el contenedor no aplican reglas de orden.
Limitaciones de la mezcla de colecciones
No puedes mezclar tipos de colección distintos, como un Map y un List. Si lo intentas, se lanza una Exception apropiada. El atributo merge debe especificarse en la definición hija heredada, situada en el nivel inferior. Especificarlo en una definición de colección padre es redundante y no produce la mezcla esperada.
Colecciones fuertemente tipadas
Gracias al soporte de Java para tipos genéricos, puedes usar colecciones fuertemente tipadas. Es decir, puedes declarar un tipo Collection que solo pueda contener, por ejemplo, elementos String. Si usas Spring para inyectar una Collection fuertemente tipada en un bean, puedes aprovechar la conversión de tipos de Spring: los elementos de la colección se convierten al tipo adecuado antes de añadirse a la Collection.
La siguiente clase Java y definición de bean muestran cómo hacerlo:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
class SomeClass {
lateinit var accounts: Map<String, Float>
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
Cuando se prepara para la inyección la propiedad accounts del bean something, la información genérica sobre el tipo de elemento del Map<String, Float> fuertemente tipado está disponible mediante reflexión. Por tanto, la infraestructura de conversión de tipos de Spring reconoce los distintos elementos de valor como Float y convierte las cadenas (9.99, 2.75 y 3.99) en valores del tipo Float.
Valores nulos y cadenas vacías
Spring trata los argumentos vacíos de propiedades y elementos similares como cadenas String vacías. El siguiente fragmento de metadatos de configuración XML establece la propiedad email con el valor de cadena vacío (""):
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
El ejemplo anterior es equivalente al siguiente código Java y Kotlin:
exampleBean.setEmail("");
exampleBean.email = ""
El elemento <null/> gestiona los valores null. El siguiente listado muestra un ejemplo:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
La configuración anterior equivale al siguiente código Java y Kotlin:
exampleBean.setEmail(null);
exampleBean.email = null
Atajo XML con el espacio de nombres p
El espacio de nombres p permite utilizar atributos del elemento bean, en lugar de elementos <property/> anidados, para describir valores de propiedades, beans colaboradores o ambos.
Spring admite formatos de configuración extensibles con espacios de nombres, basados en una definición XML Schema. El formato de configuración beans explicado en este capítulo está definido en un documento XML Schema. Sin embargo, el espacio de nombres p no se define en un archivo XSD: existe únicamente en el núcleo de Spring.
El siguiente ejemplo muestra dos fragmentos XML —el primero usa el formato XML estándar y el segundo el espacio de nombres p— que producen el mismo resultado:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
El ejemplo muestra un atributo del espacio de nombres p llamado email en la definición del bean. Esto indica a Spring que debe incluir una declaración de propiedad. Como se indicó antes, el espacio de nombres p no tiene una definición de esquema; por ello, puedes establecer el nombre del atributo con el nombre de la propiedad.
El siguiente ejemplo incluye dos definiciones de bean adicionales que hacen referencia a otro bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
Este ejemplo incluye no solo un valor de propiedad mediante el espacio de nombres p, sino también un formato especial para declarar referencias a propiedades. Mientras que la primera definición usa <property name="spouse" ref="jane"/> para crear una referencia del bean john al bean jane, la segunda usa el atributo p:spouse-ref="jane" para lograr exactamente lo mismo.
En este caso, spouse es el nombre de la propiedad y la parte final -ref indica que no se trata de un valor directo, sino de una referencia a otro bean.
El espacio de nombres p no es tan flexible como el formato XML estándar. Por ejemplo, el formato para declarar referencias a propiedades entra en conflicto con propiedades cuyo nombre termina en Ref, mientras que el XML estándar no tiene ese problema. Conviene elegir el enfoque con cuidado y comunicarlo al equipo para evitar documentos XML que mezclen los tres enfoques al mismo tiempo.
Atajo XML con el espacio de nombres c
Al igual que el atajo XML del espacio de nombres p, el espacio de nombres c, introducido en Spring 3.1, permite usar atributos en línea para configurar argumentos del constructor en lugar de elementos <constructor-arg> anidados.
El siguiente ejemplo utiliza el espacio de nombres c: para lograr lo mismo que la inyección de dependencias basada en constructor:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- declaración tradicional con nombres de argumentos opcionales -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="someone@somewhere.com"/>
</bean>
<!-- declaración con espacio de nombres c y nombres de argumentos -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="someone@somewhere.com"/>
</beans>
El espacio de nombres c: usa las mismas convenciones que p: —el sufijo -ref para referencias a beans— al establecer argumentos de constructor por su nombre. Del mismo modo, debe declararse en el archivo XML aunque no esté definido en un esquema XSD, porque existe dentro del núcleo de Spring.
En los casos poco frecuentes en que los nombres de argumentos de constructor no estén disponibles —normalmente si el bytecode se compiló sin la opción -parameters— puedes recurrir a los índices de argumentos:
<!-- declaración c-namespace mediante índices -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="someone@somewhere.com"/>
Debido a la gramática XML, la notación por índice requiere el prefijo _, porque los nombres de atributos XML no pueden comenzar con un número, aunque algunos IDE lo permitan. También existe una notación por índice para los elementos <constructor-arg>, pero no se usa habitualmente, ya que normalmente basta con el orden simple de las declaraciones.
En la práctica, el mecanismo de resolución de constructores es muy eficiente al asociar argumentos. Salvo que realmente lo necesites, se recomienda usar la notación por nombre en toda la configuración.
Nombres de propiedades compuestos
Puedes usar nombres de propiedades compuestos o anidados al establecer propiedades de beans, siempre que todos los componentes de la ruta salvo el nombre final de la propiedad no sean null. Considera la siguiente definición de bean:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
El bean something tiene una propiedad fred, que tiene una propiedad bob, que a su vez tiene una propiedad sammy; esta última se establece con el valor 123. Para que funcione, la propiedad fred de something y la propiedad bob de fred no deben ser null después de construir el bean. En caso contrario, se lanza una NullPointerException.