Customizing The Nature Of A Bean
Personalizar la naturaleza de un bean
Spring Framework proporciona varias interfaces para personalizar la naturaleza de un bean. Esta sección las agrupa así:
- Devoluciones de llamada del ciclo de vida.
ApplicationContextAwareyBeanNameAware.- Otras interfaces
Aware.
Devoluciones de llamada del ciclo de vida
Para interactuar con la gestión del ciclo de vida de un bean por parte del contenedor, puedes implementar las interfaces de Spring InitializingBean y DisposableBean. El contenedor llama a afterPropertiesSet() en la primera y a destroy() en la segunda para permitir que el bean realice acciones durante su inicialización y destrucción.
Por norma general, las anotaciones JSR-250 @PostConstruct y @PreDestroy se consideran la mejor práctica para recibir devoluciones de llamada del ciclo de vida en una aplicación Spring moderna. El uso de estas anotaciones evita acoplar los beans a interfaces específicas de Spring.
Si no quieres utilizar las anotaciones JSR-250 pero también quieres evitar el acoplamiento, considera los metadatos init-method y destroy-method de las definiciones de beans.
Internamente, Spring Framework utiliza implementaciones de BeanPostProcessor para procesar las interfaces de devolución de llamada que encuentra e invocar los métodos apropiados. Si necesitas funcionalidades personalizadas u otro comportamiento de ciclo de vida que Spring no ofrezca de forma predeterminada, puedes implementar tu propio BeanPostProcessor.
Además de las devoluciones de llamada de inicialización y destrucción, los objetos gestionados por Spring también pueden implementar la interfaz Lifecycle para participar en los procesos de inicio y parada dirigidos por el ciclo de vida del propio contenedor.
Devoluciones de llamada de inicialización
La interfaz org.springframework.beans.factory.InitializingBean permite que un bean realice trabajo de inicialización después de que el contenedor haya establecido todas las propiedades necesarias. La interfaz InitializingBean especifica un único método:
void afterPropertiesSet() throws Exception;
Se recomienda no utilizar InitializingBean, porque acopla innecesariamente el código a Spring. Como alternativa, utiliza la anotación @PostConstruct o especifica un método de inicialización de un POJO. En la configuración basada en XML, puedes utilizar el atributo init-method para indicar el nombre de un método void sin argumentos. En la configuración Java, puedes usar el atributo initMethod de @Bean.
Considera el siguiente ejemplo:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// Realiza trabajo de inicialización.
}
}
El ejemplo anterior tiene prácticamente el mismo efecto que el siguiente:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// Realiza trabajo de inicialización.
}
}
Sin embargo, el primer enfoque no acopla el código a Spring.
Ten en cuenta que @PostConstruct y los métodos de inicialización, en general, se ejecutan dentro del bloqueo de creación de singletons del contenedor. La instancia del bean solo se considera totalmente inicializada y lista para publicarse a otros componentes después de que el método @PostConstruct haya terminado.
Estos métodos de inicialización individuales deben utilizarse únicamente para validar el estado de la configuración y, posiblemente, preparar estructuras de datos a partir de dicha configuración. No deben realizar actividades adicionales que requieran acceder a beans externos, ya que existe el riesgo de provocar un bloqueo de inicialización.
Cuando sea necesario desencadenar actividad costosa posterior a la inicialización, por ejemplo, pasos asíncronos de preparación de una base de datos, el bean debe implementar SmartInitializingSingleton.afterSingletonsInstantiated() o reaccionar al evento de actualización del contexto: implementando ApplicationListener<ContextRefreshedEvent> o declarando su equivalente con @EventListener(ContextRefreshedEvent.class).
Estas variantes se ejecutan después de la inicialización normal de todos los singletons y, por tanto, fuera de cualquier bloqueo de creación de singletons. Como alternativa, puedes implementar la interfaz (Smart)Lifecycle e integrarte con la gestión global del ciclo de vida del contenedor, incluido un mecanismo de inicio automático, una fase de parada previa a la destrucción y posibles devoluciones de llamada de parada y reinicio.
Devoluciones de llamada de destrucción
La implementación de la interfaz org.springframework.beans.factory.DisposableBean permite que un bean reciba una devolución de llamada cuando se destruye el contenedor que lo contiene. La interfaz DisposableBean especifica un único método:
void destroy() throws Exception;
Se recomienda no utilizar la interfaz de devolución de llamada DisposableBean, porque acopla innecesariamente el código a Spring. Como alternativa, utiliza la anotación @PreDestroy o especifica un método genérico compatible con las definiciones de beans. En la configuración XML, puedes usar el atributo destroy-method en <bean/>. En la configuración Java, puedes usar el atributo destroyMethod de @Bean.
Considera la siguiente definición:
<bean id="exampleDestructionBean"
class="examples.ExampleBean"
destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// Realiza trabajo de destrucción, como liberar conexiones agrupadas.
}
}
La definición anterior tiene prácticamente el mismo efecto que la siguiente:
<bean id="exampleDestructionBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// Realiza trabajo de destrucción, como liberar conexiones agrupadas.
}
}
Sin embargo, la primera definición no acopla el código a Spring.
Spring también admite la inferencia de métodos de destrucción, detectando un método público close o shutdown. Este es el comportamiento predeterminado para métodos @Bean en clases de configuración Java y detecta automáticamente implementaciones de java.lang.AutoCloseable o java.io.Closeable, sin acoplar la lógica de destrucción a Spring.
Para la inferencia de métodos de destrucción en XML, puedes asignar el valor especial (inferred) al atributo destroy-method de <bean>. Esto indica a Spring que detecte automáticamente un método público close o shutdown en la clase del bean para esa definición concreta. También puedes asignar ese valor especial al atributo default-destroy-method de <beans> para aplicar el comportamiento a un conjunto completo de definiciones de beans.
Para fases de parada más amplias, puedes implementar la interfaz Lifecycle y recibir una señal de parada temprana antes de que se invoquen los métodos de destrucción de los beans singleton. También puedes implementar SmartLifecycle para disponer de una fase de parada con límite de tiempo, durante la cual el contenedor espera a que terminen todos los procesos de parada antes de continuar con los métodos de destrucción.
Métodos predeterminados de inicialización y destrucción
Cuando escribes devoluciones de llamada de inicialización y destrucción que no utilizan las interfaces específicas de Spring InitializingBean y DisposableBean, es habitual emplear nombres como init(), initialize(), dispose() y similares. Idealmente, los nombres de esos métodos deben estandarizarse en todo el proyecto para que todos los desarrolladores usen las mismas convenciones.
Puedes configurar el contenedor de Spring para que busque determinados nombres de métodos de inicialización y destrucción en todos los beans. Así puedes escribir clases de aplicación que utilicen, por ejemplo, una devolución de llamada de inicialización llamada init(), sin tener que configurar init-method="init" en cada definición de bean. El contenedor IoC de Spring llama a ese método cuando se crea el bean, de acuerdo con el contrato estándar de ciclo de vida descrito anteriormente.
Esta característica también impone una convención coherente para los nombres de los métodos de devolución de llamada.
Supón que tus métodos de inicialización se llaman init() y tus métodos de destrucción se llaman destroy(). La clase podría ser la siguiente:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// Este es el método de devolución de llamada de inicialización.
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException(
"La propiedad [blogDao] debe estar establecida.");
}
}
}
Después puedes utilizar esa clase en un bean como el siguiente:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao"/>
</bean>
</beans>
La presencia del atributo default-init-method en el elemento raíz <beans/> hace que el contenedor IoC de Spring reconozca un método llamado init en la clase del bean como devolución de llamada de inicialización. Cuando se crea y ensambla un bean, si su clase tiene ese método, se invoca en el momento apropiado.
Puedes configurar de forma similar las devoluciones de llamada de destrucción mediante el atributo default-destroy-method del elemento raíz <beans/>.
Cuando las clases de bean existentes ya tienen métodos de devolución de llamada con nombres que no siguen la convención, puedes sustituir el valor predeterminado indicando el nombre del método mediante los atributos init-method y destroy-method del propio <bean/>.
El contenedor de Spring garantiza que una devolución de llamada de inicialización configurada se invoque inmediatamente después de que el bean reciba todas sus dependencias. Por tanto, la devolución de llamada se invoca sobre la referencia sin procesar del bean, lo que significa que los interceptores AOP y otros mecanismos similares todavía no se han aplicado al bean.
Primero se crea por completo el bean objetivo y después se aplica un proxy AOP, por ejemplo, junto con su cadena de interceptores. Si el bean objetivo y el proxy se definen por separado, el código incluso puede interactuar con el bean objetivo sin procesar y omitir el proxy. Por ello, aplicar interceptores al método init sería incoherente, ya que acoplaría el ciclo de vida del bean objetivo a su proxy o interceptores y produciría una semántica extraña cuando el código interactuara directamente con el bean objetivo sin procesar.
Combinar mecanismos de ciclo de vida
Desde Spring 2.5 tienes tres opciones para controlar el comportamiento del ciclo de vida de un bean:
- Las interfaces de devolución de llamada
InitializingBeanyDisposableBean. - Métodos personalizados
init()ydestroy(). - Las anotaciones
@PostConstructy@PreDestroy.
Puedes combinar estos mecanismos para controlar un bean determinado. Si se configuran varios mecanismos de ciclo de vida para un bean y cada uno utiliza un nombre de método diferente, se ejecuta cada método configurado en el orden que se indica a continuación. Sin embargo, si se configura el mismo nombre de método —por ejemplo, init()— en más de un mecanismo, ese método se ejecuta una sola vez.
Varios mecanismos de ciclo de vida configurados para el mismo bean, con métodos de inicialización diferentes, se invocan en este orden:
- Métodos anotados con
@PostConstruct. afterPropertiesSet(), definido por la interfaz de devolución de llamadaInitializingBean.- Un método
init()personalizado configurado.
Los métodos de destrucción se invocan en el mismo orden:
- Métodos anotados con
@PreDestroy. destroy(), definido por la interfaz de devolución de llamadaDisposableBean.- Un método
destroy()personalizado configurado.
Devoluciones de llamada de inicio y parada
La interfaz Lifecycle define los métodos esenciales para cualquier objeto que tenga requisitos propios de ciclo de vida, como iniciar y detener un proceso en segundo plano:
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
Cualquier objeto gestionado por Spring puede implementar la interfaz Lifecycle. Cuando el propio ApplicationContext recibe señales de inicio y parada, por ejemplo en un escenario de parada y reinicio durante la ejecución, transmite esas llamadas a todas las implementaciones de Lifecycle definidas en ese contexto. Lo hace delegando en un LifecycleProcessor:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
LifecycleProcessor es una extensión de la interfaz Lifecycle. También añade otros dos métodos para reaccionar a la actualización y al cierre del contexto.
La interfaz normal org.springframework.context.Lifecycle es un contrato sencillo para notificaciones explícitas de inicio y parada; no implica inicio automático durante la actualización del contexto. Para un control más detallado del inicio automático y una parada ordenada de un bean concreto, incluidos los pasos de inicio y parada, considera implementar la interfaz extendida org.springframework.context.SmartLifecycle.
Las notificaciones de parada no siempre se garantizan antes de la destrucción. Durante una parada normal, todos los beans Lifecycle reciben primero una notificación de parada antes de que se propaguen las devoluciones de llamada generales de destrucción. Sin embargo, durante una actualización en caliente a lo largo de la vida del contexto o al detener intentos de actualización, solo se llaman los métodos de destrucción.
El orden de las invocaciones de inicio y parada puede ser importante. Si existe una relación depends-on entre dos objetos, el objeto dependiente se inicia después de su dependencia y se detiene antes que ella. Sin embargo, a veces se desconocen las dependencias directas. Puede que solo sepas que los objetos de un tipo deben iniciarse antes que los objetos de otro tipo. En esos casos, la interfaz SmartLifecycle proporciona otra opción: el método getPhase(), definido en su superinterfaz Phased.
La interfaz Phased se define así:
public interface Phased {
int getPhase();
}
La interfaz SmartLifecycle se define así:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
Durante el inicio, los objetos con la fase más baja se inician primero. Durante la parada, se sigue el orden inverso. Por tanto, un objeto que implemente SmartLifecycle y cuyo método getPhase() devuelva Integer.MIN_VALUE estará entre los primeros en iniciarse y los últimos en detenerse.
En el extremo contrario, un valor de fase Integer.MAX_VALUE indica que el objeto debe iniciarse el último y detenerse el primero, probablemente porque depende de que otros procesos estén en ejecución.
También debes tener en cuenta que la fase predeterminada para cualquier objeto Lifecycle normal que no implemente SmartLifecycle es 0. Por tanto, un valor de fase negativo indica que el objeto debe iniciarse antes que esos componentes estándar y detenerse después. Lo contrario ocurre con los valores de fase positivos.
El método stop definido por SmartLifecycle acepta una devolución de llamada. Cualquier implementación debe invocar run() en esa devolución de llamada después de completar su proceso de apagado. Esto permite realizar paradas asíncronas cuando sea necesario, ya que la implementación predeterminada de LifecycleProcessor, DefaultLifecycleProcessor, espera hasta su valor de tiempo de espera a que el grupo de objetos de cada fase invoque esa devolución de llamada. El tiempo de espera predeterminado por fase es de 30 segundos.
Puedes sustituir la instancia predeterminada del procesador de ciclo de vida definiendo un bean llamado lifecycleProcessor en el contexto. Si solo quieres modificar el tiempo de espera, bastaría con definir lo siguiente:
<bean id="lifecycleProcessor"
class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- Valor de tiempo de espera en milisegundos. -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
Como se ha indicado anteriormente, la interfaz LifecycleProcessor también define métodos de devolución de llamada para la actualización y el cierre del contexto. El cierre dirige el proceso de apagado como si se hubiera llamado explícitamente a stop(), pero sucede cuando el contexto se está cerrando.
Por otro lado, la devolución de llamada de actualización permite otra característica de los beans SmartLifecycle. Cuando el contexto se actualiza, después de que todos los objetos se hayan instanciado e inicializado, se invoca dicha devolución de llamada.
En ese momento, el procesador de ciclo de vida predeterminado comprueba el valor booleano que devuelve el método isAutoStartup() de cada objeto SmartLifecycle. Si devuelve true, el objeto se inicia en ese momento en vez de esperar una llamada explícita a start() sobre el contexto o sobre el propio objeto. A diferencia de la actualización del contexto, el inicio del contexto no se produce automáticamente en una implementación de contexto estándar. El valor de phase y las relaciones depends-on determinan el orden de inicio.
Cerrar correctamente el contenedor IoC de Spring en aplicaciones no web
Esta sección se aplica únicamente a aplicaciones que no son web. Las implementaciones web de ApplicationContext de Spring ya contienen el código necesario para cerrar correctamente el contenedor IoC de Spring cuando se detiene la aplicación web correspondiente.
Si utilizas el contenedor IoC de Spring en un entorno de aplicación no web, por ejemplo en una aplicación de escritorio enriquecida, registra un shutdown hook en la JVM. Esto garantiza un cierre ordenado e invoca los métodos de destrucción pertinentes de tus beans singleton para que se liberen todos los recursos. Aun así, debes configurar e implementar correctamente esas devoluciones de llamada de destrucción.
Para registrar un shutdown hook, llama al método registerShutdownHook() declarado en la interfaz ConfigurableApplicationContext:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx =
new ClassPathXmlApplicationContext("beans.xml");
// Añade un shutdown hook para el contexto anterior.
ctx.registerShutdownHook();
// La aplicación se ejecuta aquí.
// El método main termina; el hook se llama antes de apagar la aplicación.
}
}
Seguridad de hilos y visibilidad
El contenedor principal de Spring publica las instancias singleton creadas de forma segura para hilos, protege el acceso mediante un bloqueo singleton y garantiza su visibilidad en otros hilos.
Como consecuencia, las clases de beans proporcionadas por la aplicación no necesitan preocuparse por la visibilidad de su estado de inicialización. Los campos de configuración normales no tienen que marcarse como volatile siempre que solo se modifiquen durante la fase de inicialización. Esto proporciona garantías de visibilidad similares a final, incluso para el estado de configuración basado en setters que es mutable durante esa fase inicial.
Si esos campos se modifican después de la fase de creación del bean y de su publicación inicial posterior, deben declararse como volatile o protegerse con un bloqueo común cada vez que se acceda a ellos.
El acceso concurrente a ese estado de configuración en instancias singleton de beans, por ejemplo, instancias de controladores o repositorios, es completamente seguro para hilos después de que el contenedor realice esa publicación inicial segura. Esto también incluye las instancias singleton habituales de FactoryBean, que se procesan dentro del bloqueo singleton general.
Para las devoluciones de llamada de destrucción, el estado de configuración sigue siendo seguro para hilos, pero cualquier estado de ejecución acumulado entre la inicialización y la destrucción debe mantenerse en estructuras seguras para hilos, o en campos volatile cuando se trate de casos simples, de acuerdo con las prácticas habituales de Java.
Una integración más profunda con Lifecycle, como la descrita anteriormente, implica estado mutable durante la ejecución, como un campo runnable, que debe declararse como volatile.
Aunque las devoluciones de llamada habituales del ciclo de vida siguen un determinado orden —por ejemplo, se garantiza que una devolución de llamada de inicio ocurre solo después de la inicialización completa y una devolución de llamada de parada solo después de un inicio inicial—, existe un caso especial en el patrón habitual de parada antes de destrucción.
Se recomienda encarecidamente que el estado interno de cualquier bean de este tipo también permita una devolución de llamada inmediata de destrucción sin una parada previa. Esto puede ocurrir durante un apagado extraordinario tras un arranque cancelado o en caso de que otro bean provoque un tiempo de espera agotado durante la parada.
ApplicationContextAware y BeanNameAware
Cuando un ApplicationContext crea una instancia de objeto que implementa la interfaz org.springframework.context.ApplicationContextAware, dicha instancia recibe una referencia a ese ApplicationContext. La interfaz ApplicationContextAware se define así:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext)
throws BeansException;
}
Por tanto, los beans pueden manipular mediante programación el ApplicationContext que los creó, a través de la interfaz ApplicationContext o convirtiendo la referencia a una subclase conocida de esa interfaz, como ConfigurableApplicationContext, que expone funcionalidad adicional.
Un uso posible es recuperar otros beans mediante programación. A veces esa capacidad resulta útil. Sin embargo, en general debes evitarla, porque acopla el código a Spring y no sigue el estilo de inversión de control, en el que los colaboradores se proporcionan a los beans como propiedades.
Otros métodos de ApplicationContext permiten acceder a recursos de archivos, publicar eventos de aplicación y acceder a un MessageSource.
La autoconexión es otra alternativa para obtener una referencia al ApplicationContext. Los modos tradicionales de autoconexión constructor y byType pueden proporcionar una dependencia de tipo ApplicationContext a un argumento de constructor o a un parámetro de un método setter, respectivamente.
Para mayor flexibilidad, incluida la posibilidad de autoconectar campos y métodos con varios parámetros, utiliza las funciones de autoconexión basadas en anotaciones.
En tal caso, ApplicationContext se autoconecta en un campo, argumento de constructor o parámetro de método que espere el tipo ApplicationContext, siempre que el campo, constructor o método correspondiente incluya la anotación @Autowired.
Cuando un ApplicationContext crea una clase que implementa la interfaz org.springframework.beans.factory.BeanNameAware, dicha clase recibe una referencia al nombre definido en su definición de objeto asociada. La interfaz BeanNameAware se define así:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
La devolución de llamada se invoca después de completar las propiedades normales del bean, pero antes de una devolución de llamada de inicialización como InitializingBean.afterPropertiesSet() o un init-method personalizado.
Otras interfaces Aware
Además de ApplicationContextAware y BeanNameAware, Spring ofrece una amplia variedad de interfaces de devolución de llamada Aware que permiten a los beans indicar al contenedor que necesitan una dependencia concreta de infraestructura. Como regla general, el nombre de la interfaz indica el tipo de dependencia.
Interfaz Aware |
Dependencia inyectada |
|---|---|
ApplicationContextAware |
El ApplicationContext que declara el bean. |
ApplicationEventPublisherAware |
El publicador de eventos del ApplicationContext contenedor. |
BeanClassLoaderAware |
El cargador de clases utilizado para cargar las clases de bean. |
BeanFactoryAware |
El BeanFactory que declara el bean. |
BeanNameAware |
El nombre del bean que lo declara. |
LoadTimeWeaverAware |
El weaver definido para procesar la definición de clases durante la carga. |
MessageSourceAware |
La estrategia configurada para resolver mensajes, con soporte para parametrización e internacionalización. |
NotificationPublisherAware |
El publicador de notificaciones JMX de Spring. |
ResourceLoaderAware |
El cargador configurado para acceder a recursos de bajo nivel. |
ServletConfigAware |
El ServletConfig actual en el que se ejecuta el contenedor. Solo es válido en un ApplicationContext de Spring compatible con aplicaciones web. |
ServletContextAware |
El ServletContext actual en el que se ejecuta el contenedor. Solo es válido en un ApplicationContext de Spring compatible con aplicaciones web. |
El uso de estas interfaces acopla el código a la API de Spring y no sigue el estilo de inversión de control. Por ello, se recomiendan para beans de infraestructura que necesiten acceso programático al contenedor.