Diferencia entre revisiones de «Ejb Con Spring»

De Dos Ideas.
Saltar a: navegación, buscar
(El EJB)
 
(No se muestran 17 ediciones intermedias de 3 usuarios)
Línea 1: Línea 1:
 +
[[Category:Spring Framework]]
 +
[[Category:EJB]]
 +
 
[[Spring Framework]] trae amplio soporte para el uso de [[EJB]].
 
[[Spring Framework]] trae amplio soporte para el uso de [[EJB]].
 +
 +
{{curso|url=http://www.dosideas.com/cursos/course/view.php?id=5|nombre=Introducción a EJB 3.0 de sesión}}
  
 
==Creación en EJB 2.x==
 
==Creación en EJB 2.x==
Línea 18: Línea 23:
 
Supondremos que ya contamos con un objeto de negocio FlotaEspacialBO de la siguiente forma:
 
Supondremos que ya contamos con un objeto de negocio FlotaEspacialBO de la siguiente forma:
  
 +
<code java5>
 
  public class FlotaEspacialBoImpl implements FlotaEspacialBo {
 
  public class FlotaEspacialBoImpl implements FlotaEspacialBo {
 
     public Collection<Invasor> buscarInvasores(Long idFlota) {
 
     public Collection<Invasor> buscarInvasores(Long idFlota) {
Línea 23: Línea 29:
 
     }
 
     }
 
  }
 
  }
 +
</code>
  
 
Expondremos este EJB (el cual lo tenemos ya declarado en Spring) usando un EJB Stateless. Para el EJB crearemos todas las interafaces necesarias (Home, Remote, Local, etc). La diferencia estará en el bean:
 
Expondremos este EJB (el cual lo tenemos ya declarado en Spring) usando un EJB Stateless. Para el EJB crearemos todas las interafaces necesarias (Home, Remote, Local, etc). La diferencia estará en el bean:
<pre>
+
<code java5>
 
  public class FlotaEspacialBean extends AbstractStatelessSessionBean {
 
  public class FlotaEspacialBean extends AbstractStatelessSessionBean {
  
Línea 36: Línea 43:
 
     @Override
 
     @Override
 
     protected void onEjbCreate() throws CreateException {
 
     protected void onEjbCreate() throws CreateException {
         flotaEspcialBo = (!FlotaEspacialBo) getBeanFactory().getBean("business.FlotaEspacialBo");
+
         flotaEspcialBo = (FlotaEspacialBo) getBeanFactory().getBean("business.FlotaEspacialBo");
 
     }
 
     }
 
  }
 
  }
</pre>
+
</code>
 +
 
 
Como se ve, el bean hereda de !AbstractStatelessSessionBean, clase que ya provee varios métodos implementados para los EJB. Así, el EJB queda muy simple, invocando al objeto de negocio directamente para resolver la lógica. El método ''onEjbCreate()'' se encarga de inyectar las dependencias del EJB utilizando el método ''getBeanFactory()'' que viene heredado.
 
Como se ve, el bean hereda de !AbstractStatelessSessionBean, clase que ya provee varios métodos implementados para los EJB. Así, el EJB queda muy simple, invocando al objeto de negocio directamente para resolver la lógica. El método ''onEjbCreate()'' se encarga de inyectar las dependencias del EJB utilizando el método ''getBeanFactory()'' que viene heredado.
  
 
Falta entonces indicarle a Spring los archivos de configuración que deberá levantar el factory, lo cual va en el ''ejb-jar.xml''
 
Falta entonces indicarle a Spring los archivos de configuración que deberá levantar el factory, lo cual va en el ''ejb-jar.xml''
  
<pre>
+
<code xml>
 
  <ejb-jar>
 
  <ejb-jar>
 
     <enterprise-beans>
 
     <enterprise-beans>
Línea 62: Línea 70:
 
     </enterprise-beans>
 
     </enterprise-beans>
 
  </ejb-jar>
 
  </ejb-jar>
</pre>
+
</code>
  
 
Los tag ''env-entry'' contienen la ubicación de los archivos de Spring para inicializar.
 
Los tag ''env-entry'' contienen la ubicación de los archivos de Spring para inicializar.
 
 
  
 
== Creación en EJB 3.x ==
 
== Creación en EJB 3.x ==
 
EJB 3 contiene muchas mejoras y simplificaciones al momento de crear EJBs. Integrar EJB3 y Spring es una tarea muy sencilla, que se resuelve con el uso de Anotaciones.
 
EJB 3 contiene muchas mejoras y simplificaciones al momento de crear EJBs. Integrar EJB3 y Spring es una tarea muy sencilla, que se resuelve con el uso de Anotaciones.
  
<pre>
+
<code java5>
 
  import javax.interceptor.Interceptors;
 
  import javax.interceptor.Interceptors;
 
  import org.springframework.beans.factory.annotation.Autowired;
 
  import org.springframework.beans.factory.annotation.Autowired;
Línea 86: Línea 92:
 
     }
 
     }
 
  }
 
  }
</pre>
+
</code>
  
 
La anotacion ''@Interceptors(SpringBeanAutowiringInterceptor.class)'' prepara un factory para ser usado en el EJB. A su vez, se encarga de inyectar todos los atributos marcados con ''@Autowired''.
 
La anotacion ''@Interceptors(SpringBeanAutowiringInterceptor.class)'' prepara un factory para ser usado en el EJB. A su vez, se encarga de inyectar todos los atributos marcados con ''@Autowired''.
Línea 92: Línea 98:
 
El interceptor busca un archivo ''beanRefContext.xml'' en el classpath, que contenga una instancia de un ApplicationContext, el cual referencia a los archivos de Spring a cargar. Por ejemplo:
 
El interceptor busca un archivo ''beanRefContext.xml'' en el classpath, que contenga una instancia de un ApplicationContext, el cual referencia a los archivos de Spring a cargar. Por ejemplo:
  
<pre>
+
<code xml>
 
  <?xml version="1.0" encoding="UTF-8"?>
 
  <?xml version="1.0" encoding="UTF-8"?>
 
  <beans>
 
  <beans>
Línea 106: Línea 112:
 
     </bean>
 
     </bean>
 
  </beans>
 
  </beans>
</pre>
+
</code>
  
 
== Acceso de EJBs ==
 
== Acceso de EJBs ==
Línea 115: Línea 121:
 
Para esto, se utilizan 2 clases principales:
 
Para esto, se utilizan 2 clases principales:
 
* [[JndiTemplate]], que contiene la información para realizar la búsqueda del EJB.
 
* [[JndiTemplate]], que contiene la información para realizar la búsqueda del EJB.
* Simple Remote Stateless Session Proxy Factory Bean, que realizará la instanciación de nuestro EJB. Este Proxy será el que inyectaremos en nuestros otros beans.
+
* En el caso de EJB 2.x, usaremos la clase ''SimpleRemoteStatelessSessionProxyFactoryBean'', que realizará la instanciación de nuestro EJB. Este Proxy será el que inyectaremos en nuestros otros beans.
 +
* En el caso de EJB 3.x, usaremos la clase ''JndiObjectFactoryBean'', que realizará la búsqueda del EJB. Este proxy será el que inyectaremos en nuestros beans.
  
===Ejemplo de acceso===
+
===Ejemplo de acceso a EJB 2.x===
 +
Este ejemplo funciona con EJB 2.x.
  
 
====El EJB====
 
====El EJB====
Línea 124: Línea 132:
 
* com.dosideas.business.ejb.persona.PersonaServiceBean (implementación del EJB)
 
* com.dosideas.business.ejb.persona.PersonaServiceBean (implementación del EJB)
  
=Archivo de configuración de Spring=
+
====Archivo de configuración de Spring====
 
En un archivo de Spring, podemos declarar una referencia a nuestro EJB, que luego podremos inyectar como un bean normal.
 
En un archivo de Spring, podemos declarar una referencia a nuestro EJB, que luego podremos inyectar como un bean normal.
  
 +
<code xml>
 
  <?xml version="1.0" encoding="UTF-8"?>
 
  <?xml version="1.0" encoding="UTF-8"?>
 
  <beans>
 
  <beans>
 +
    <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate"/>
  
     <bean id="jndiTemplate" class="org.springframework.jndi.!JndiTemplate">
+
     <bean id="ejb.PersonaServiceBean"  
    </bean>
+
          class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
 
 
    <bean  id="ejb.!PersonaServiceBean" class="org.springframework.ejb.access.!SimpleRemoteStatelessSessionProxyFactoryBean">
 
 
         <property name="jndiName">
 
         <property name="jndiName">
             <value>ejb/!PersonaServiceBean</value>
+
             <value>ejb/PersonaServiceBean</value>
 
         </property>
 
         </property>
 
         <property name="jndiTemplate">
 
         <property name="jndiTemplate">
Línea 141: Línea 149:
 
         </property>
 
         </property>
 
         <property name="businessInterface">
 
         <property name="businessInterface">
             <value>com.dosideas.business.ejb.persona.!PersonaServiceRemote</value>
+
             <value>com.dosideas.business.ejb.persona.PersonaServiceRemote</value>
 
         </property>
 
         </property>
 
     </bean>
 
     </bean>
 
  </beans>
 
  </beans>
 +
</code>
  
Listo! El bean con id "ejb.!PersonaServiceBean" es un proxy que cumple con la interfaz indicada en el atributo "businessInterface". En el ejemplo, se declaró la interfaz de negocio remota del EJB. Este bean puede ya ser inyectado en cualquier otro lado y utilizado normalmente.
+
El bean con id ''ejb.PersonaServiceBean'' es un proxy que cumple con la interfaz indicada en el atributo "businessInterface". En el ejemplo, se declaró la interfaz de negocio remota del EJB. Este bean puede ya ser inyectado en cualquier otro lado y utilizado normalmente.
  
 
Spring realizará la creación del bean igual que con el resto de los objetos. En este caso, al iniciarse el factory de Spring se realizará el lookup e instanciación correspondiente del EJB. Es posible demorar esta acción con los medios tradicionales que provee Spring (lazy-init, etc.).
 
Spring realizará la creación del bean igual que con el resto de los objetos. En este caso, al iniciarse el factory de Spring se realizará el lookup e instanciación correspondiente del EJB. Es posible demorar esta acción con los medios tradicionales que provee Spring (lazy-init, etc.).
  
=El atributo businessInterface=
+
====El atributo businessInterface====
 
El atributo businessInterface indica la interfaz que implementará el Proxy de Spring. Un detalle interesante es que esta interfaz no tiene porqué ser la interfaz de negocio del EJB.
 
El atributo businessInterface indica la interfaz que implementará el Proxy de Spring. Un detalle interesante es que esta interfaz no tiene porqué ser la interfaz de negocio del EJB.
  
 
Así, podriamos crear una interfaz "nuestra", que cumpla con los métodos que están en la interfaz de negocio del EJB. De esta manera, nuestra aplicación queda independiente de cambios en la interfaz del EJB (y la posiblidad, además, de intercambiar libremente entre interfaces remotas y locales del EJB).
 
Así, podriamos crear una interfaz "nuestra", que cumpla con los métodos que están en la interfaz de negocio del EJB. De esta manera, nuestra aplicación queda independiente de cambios en la interfaz del EJB (y la posiblidad, además, de intercambiar libremente entre interfaces remotas y locales del EJB).
  
===Ver también===
+
===Ejemplo de acceso a EJB 3.x===
 +
Este ejemplo funciona con EJB 3.x.
 +
 
 +
====El EJB====
 +
Supongamos que tenemos un EJB llamado ''PersonaServiceBean''. Esta EJB está compuesto por la siguiente interfaz:
 +
* com.dosideas.business.ejb.persona.PersonaServiceRemote (interfaz de negocio remota)
 +
 
 +
 
 +
====Archivo de configuración de Spring====
 +
En un archivo de Spring, podemos declarar una referencia a nuestro EJB, que luego podremos inyectar como un bean normal.
 +
 
 +
<code xml>
 +
<?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-2.0.xsd">
 +
 
 +
    <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate" />
 +
 
 +
    <bean id="ejb.PersonaServiceBean" class="org.springframework.jndi.JndiObjectFactoryBean">
 +
        <property name="jndiName" value="ejb/PersonaServiceBean"/>
 +
    </bean>
 +
</beans>
 +
</code>
 +
 
 +
Una forma alternativa es utilizando los tags propios para jee que trae Spring.
 +
 
 +
<code xml>
 +
<jee:jndi-lookup id="ejb.PersonaServiceBean" jndi-name="ejb/PersonaServiceBean" cache="true" />
 +
</code>
 +
 
 +
== Reconexión automática de EJBs ==
 +
Si por algún motivo se reinicia el servidor de EJBs, podría ser necesario reiniciar también a los clientes.
 +
 
 +
Podemos usar el atributo ''refresh-home-on-connect-failure'' para forzar una nueva búsqueda de nuestro cliente en el caso de un error de conexión. Para EJB 3.0, esto funciona a partir de Spring 2.5.5 (ya que las versiones anteriores contenían un bug).
 +
 
 +
<code xml>
 +
<jee:remote-slsb id="miServicio" jndi-name="ejb/MiServicio" 
 +
    business-interface="com.dosideas.business.ejb.MiServicio" 
 +
    cache-home="false"
 +
    lookup-home-on-startup="false" 
 +
    home-interface="com.dosideas.business.ejb.MiServicioHome" 
 +
    resource-ref="false"
 +
    refresh-home-on-connect-failure="true">
 +
    <jee:environment> 
 +
        <!-- Incluir información del entorno aquí --> 
 +
    </jee:environment> 
 +
</jee:remote-slsb> 
 +
</code>
 +
 
 +
==Ver también==
 
* [[EJB]]
 
* [[EJB]]
 
* [[MockEjb]]
 
* [[MockEjb]]
 
* [[JndiTemplate]]
 
* [[JndiTemplate]]
 
+
* [http://static.springframework.org/spring/docs/2.5.x/reference/ejb.html Manual de Referencia de Spring, Capítulo 18: integración de EJB]
===Más información===
+
* [http://www.javaworld.com/javaworld/jw-02-2005/jw-0214-springejb.html Pro Spring: Spring and EJB]
* [[http://static.springframework.org/spring/docs/2.5.x/reference/ejb.html Manual de Referencia de Spring, Capítulo 18: integración de EJB]]
 
* [[http://www.javaworld.com/javaworld/jw-02-2005/jw-0214-springejb.html Pro Spring: Spring and EJB]]
 

Revisión actual del 15:20 26 ago 2009

Spring Framework trae amplio soporte para el uso de EJB.

Introducción a EJB 3.0 de sesión
Visitá el taller donde encontrarás más información, ejemplos y prácticas sobre este tema.

Creación en EJB 2.x

Spring provee una forma simple y cómoda para crear EJBs de Stateless, Stateful y Message Driven Bean. Esto se hace heredando la implementación de los EJB de alguna de estas clases:

  • Abstract Stateless Session Bean
  • Abstract Stateful Session Bean
  • Abstract Message Driven Bean

Estas clases proveen varios beneficios:

  • proveen acceso a un factory de Spring ya inicializado
  • implementan ya varios métodos obligatorios de EJB

Las clases brindan un factory de Spring ya creado e inicializado, el cual se accede a través del método heredado getBeanFactory(). Este factory se configura a través de variables de entorno en el archivo ejb-jar.xml.

El método ejbCreate() se encuentra implementado (para poder inicializar el factory y otras tareas). Spring provee el método onEjbCreate() el cual implementaremos para inyectar "a mano" las dependencias de nuestro EJB.

Ejemplo de creación de un EJB Stateless

Supondremos que ya contamos con un objeto de negocio FlotaEspacialBO de la siguiente forma:

public class FlotaEspacialBoImpl implements FlotaEspacialBo {
    public Collection<Invasor> buscarInvasores(Long idFlota) {
        ... buscar los invasores de la flota indicada ...
    }
}

Expondremos este EJB (el cual lo tenemos ya declarado en Spring) usando un EJB Stateless. Para el EJB crearemos todas las interafaces necesarias (Home, Remote, Local, etc). La diferencia estará en el bean:

public class FlotaEspacialBean extends AbstractStatelessSessionBean {
    private FlotaEspacialBo flotaEspacialBo;
    public Collection<Invasor> buscarInvasores(Long idFlota) {
         return flotaEspcialBo.buscarInvasores(idFlota);
    }
    @Override
    protected void onEjbCreate() throws CreateException {
        flotaEspcialBo = (FlotaEspacialBo) getBeanFactory().getBean("business.FlotaEspacialBo");
    }
}

Como se ve, el bean hereda de !AbstractStatelessSessionBean, clase que ya provee varios métodos implementados para los EJB. Así, el EJB queda muy simple, invocando al objeto de negocio directamente para resolver la lógica. El método onEjbCreate() se encarga de inyectar las dependencias del EJB utilizando el método getBeanFactory() que viene heredado.

Falta entonces indicarle a Spring los archivos de configuración que deberá levantar el factory, lo cual va en el ejb-jar.xml

<ejb-jar>
   <enterprise-beans>
       <session>
           <ejb-name>FlotaEspacialBean</ejb-name>
           <home>com.dosideas.business.ejb.flota.FlotaEspacialRemoteHome</home>
           <remote>com.dosideas.business.ejb.flota.FlotaEspacialRemote</remote>
           <ejb-class>com.dosideas.business.ejb.flota.FlotaEspacialBean</ejb-class>
           <session-type>Stateless</session-type>
           <transaction-type>Container</transaction-type>
           <env-entry>
               <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
               <env-entry-type>java.lang.String</env-entry-type>
               <env-entry-value>application-ejb.xml,application-negocio.xml</env-entry-value>
           </env-entry>
       </session>
   </enterprise-beans>
</ejb-jar>

Los tag env-entry contienen la ubicación de los archivos de Spring para inicializar.

Creación en EJB 3.x

EJB 3 contiene muchas mejoras y simplificaciones al momento de crear EJBs. Integrar EJB3 y Spring es una tarea muy sencilla, que se resuelve con el uso de Anotaciones.

import javax.interceptor.Interceptors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor;
@Stateless
@Interceptors(SpringBeanAutowiringInterceptor.class)
public class PaisSessionBeanBean implements PaisSessionBeanRemote {
   @Autowired
   private PaisBo paisBo;
   public Pais buscarPaisPorId(Long id) {
       return paisBo.buscarPaisPorId(id);
   }
}

La anotacion @Interceptors(SpringBeanAutowiringInterceptor.class) prepara un factory para ser usado en el EJB. A su vez, se encarga de inyectar todos los atributos marcados con @Autowired.

El interceptor busca un archivo beanRefContext.xml en el classpath, que contenga una instancia de un ApplicationContext, el cual referencia a los archivos de Spring a cargar. Por ejemplo:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
   <bean name="applicationContext-main" class="org.springframework.context.support.ClassPathXmlApplicationContext">
       <constructor-arg>
           <list>
               <value>application-business.xml</value>
               <value>application-dao.xml</value>
               <value>application-db.xml</value>
               <value>application-hibernate.xml</value>
           </list>
       </constructor-arg>
   </bean>
</beans>

Acceso de EJBs

Uno de los usos más interesantes es la posibilidad de dejar a Spring realizar el lookup del EJB e inyectar la interfaz de negocio directamente en nuestros objetos.

Así, delegamos a Spring la localización y creación del EJB.

Para esto, se utilizan 2 clases principales:

  • JndiTemplate, que contiene la información para realizar la búsqueda del EJB.
  • En el caso de EJB 2.x, usaremos la clase SimpleRemoteStatelessSessionProxyFactoryBean, que realizará la instanciación de nuestro EJB. Este Proxy será el que inyectaremos en nuestros otros beans.
  • En el caso de EJB 3.x, usaremos la clase JndiObjectFactoryBean, que realizará la búsqueda del EJB. Este proxy será el que inyectaremos en nuestros beans.

Ejemplo de acceso a EJB 2.x

Este ejemplo funciona con EJB 2.x.

El EJB

Supongamos que tenemos un EJB llamado PersonaServiceBean. Esta EJB está compuesto de las siguiente clases:

  • com.dosideas.business.ejb.persona.PersonaServiceRemote (interfaz de negocio remota)
  • com.dosideas.business.ejb.persona.PersonaServiceBean (implementación del EJB)

Archivo de configuración de Spring

En un archivo de Spring, podemos declarar una referencia a nuestro EJB, que luego podremos inyectar como un bean normal.

<?xml version="1.0" encoding="UTF-8"?>
<beans>
   <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate"/>
   <bean id="ejb.PersonaServiceBean" 
         class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
       <property name="jndiName">
           <value>ejb/PersonaServiceBean</value>
       </property>
       <property name="jndiTemplate">
           <ref local="jndiTemplate"/>
       </property>
       <property name="businessInterface">
           <value>com.dosideas.business.ejb.persona.PersonaServiceRemote</value>
       </property>
   </bean>
</beans>

El bean con id ejb.PersonaServiceBean es un proxy que cumple con la interfaz indicada en el atributo "businessInterface". En el ejemplo, se declaró la interfaz de negocio remota del EJB. Este bean puede ya ser inyectado en cualquier otro lado y utilizado normalmente.

Spring realizará la creación del bean igual que con el resto de los objetos. En este caso, al iniciarse el factory de Spring se realizará el lookup e instanciación correspondiente del EJB. Es posible demorar esta acción con los medios tradicionales que provee Spring (lazy-init, etc.).

El atributo businessInterface

El atributo businessInterface indica la interfaz que implementará el Proxy de Spring. Un detalle interesante es que esta interfaz no tiene porqué ser la interfaz de negocio del EJB.

Así, podriamos crear una interfaz "nuestra", que cumpla con los métodos que están en la interfaz de negocio del EJB. De esta manera, nuestra aplicación queda independiente de cambios en la interfaz del EJB (y la posiblidad, además, de intercambiar libremente entre interfaces remotas y locales del EJB).

Ejemplo de acceso a EJB 3.x

Este ejemplo funciona con EJB 3.x.

El EJB

Supongamos que tenemos un EJB llamado PersonaServiceBean. Esta EJB está compuesto por la siguiente interfaz:

  • com.dosideas.business.ejb.persona.PersonaServiceRemote (interfaz de negocio remota)


Archivo de configuración de Spring

En un archivo de Spring, podemos declarar una referencia a nuestro EJB, que luego podremos inyectar como un bean normal.

<?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-2.0.xsd">
   <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate" />
   <bean id="ejb.PersonaServiceBean" class="org.springframework.jndi.JndiObjectFactoryBean">
       <property name="jndiName" value="ejb/PersonaServiceBean"/>
   </bean>
</beans>

Una forma alternativa es utilizando los tags propios para jee que trae Spring.

<jee:jndi-lookup id="ejb.PersonaServiceBean" jndi-name="ejb/PersonaServiceBean" cache="true" />

Reconexión automática de EJBs

Si por algún motivo se reinicia el servidor de EJBs, podría ser necesario reiniciar también a los clientes.

Podemos usar el atributo refresh-home-on-connect-failure para forzar una nueva búsqueda de nuestro cliente en el caso de un error de conexión. Para EJB 3.0, esto funciona a partir de Spring 2.5.5 (ya que las versiones anteriores contenían un bug).

<jee:remote-slsb id="miServicio" jndi-name="ejb/MiServicio"

   business-interface="com.dosideas.business.ejb.MiServicio"  
   cache-home="false" 
   lookup-home-on-startup="false"  
   home-interface="com.dosideas.business.ejb.MiServicioHome"  
   resource-ref="false" 
   refresh-home-on-connect-failure="true">
   <jee:environment>  
   </jee:environment>  

</jee:remote-slsb>

Ver también