Spring Bean Scopes: Singleton con Prototypes


Introducción

Esta es la segunda publicación en la seria acerca de Spring Bean Scopes. En el tutorial anterior vimos que cuando se inyectaban Beans de ámbito Prototype dentro de un Singleton  aparecían algunas trabas. El principal problema es que las dependencias Autowired se inyectan en el Singleton cuando este se instancia (evento que ocurre una sola vez) por lo que aunque el Bean inyectado se haya declarado como Prototype, en realidad se comportará como un Singleton.

El siguiente código resalta este comportamiento:

En el ejemplo anterior, a pesar de que el objeto devuelto por el método getAutowiredSample se ha declarado como un Bean de ámbito Prototype, la instancia devuelta en las dos peticiones es la misma.

Para resolver este problema disponemos de diversas alternativas.

Anotación @Lookup

La anotación @Lookup nos permite anotar métodos tipo getter para un Bean específico. El contenedor de Spring se encargará de anular este método en tiempo de ejecución y redirigirlo al BeanFactory para realizar una llamada estándar al método getBean, devolviendo el Bean que este método devolvería (Singleton, Prototype, Request, Session…). En este caso, al haber definido el Bean con ámito Prototype, para cada petición se devolverá una nueva instancia del Bean.

El siguiente fragmento de código muestra como emplear la anotación @Lookup:

El principal problema con esta alternativa es que sólo funcionara con Beans de ámbito Singleton que se hayan definido empleando anotaciones de tipo @Component y que se hayan instanciado mediante escaneo de componentes (@ComponentScan) o autodetección. No funcionará con Beans que se hayan definido con anotaciones @Bean.

El siguiente test muestra el comportamiento de la anotación @Lookup en un singleton definido con una anotación @Component:

Tal como se muestra en el fragmento anterior, este es el comportamiento que estamos esperando. Hay dos objetos creados a partir de un Singleton Bean que en realidad son la misma instancia a pesar de haberse pedido dos veces (comportamiento de los Singleton).  Cuando se hacen peticiones al método getSampleUsingLookup() se devuelven instancias diferentes de la clase Sample ya que se ha definido como un Bean de ámbito Prototype.

Tal como ya se ha comentado, hay un problema con esta técnica y es que sólo funcionará con Beans de ámbito Singleton que se hayan definido con una anotación de tipo @Component. Si el Bean se declara utilizando un procedimiento como el siguiente:

El test fallará ya que el método getSampleUsingLoopup() no se anulará por el contenedor de Spring y por lo tanto devolverá siempre null. Podemos comprobar este comportamiento con el test anterior aplicando algunas modificaciones:

Utilizando ApplicationContext

Otro sistema para sobreponerse al problema de la inyección de beans con ámbitos es inyectar ApplicationContext en el el Bean de ámbito Singleton. Siempre que se necesite una instancia del Bean de ámbito Prototype, el Singleton  hará una petición a la instancia de ApplicationContext.

El procedimiento para el Singleton sería el siguiente:

Podemos verificar el comportamiento esperado con el siguiente test:

Aunque esta técnica es efectiva, claramente vulnera los principios de SOLID al contradecir el principio de la inversión de control. Las dependencias se solicitan directamente al contenedor de Spring en lugar de inyectarse en la clase por lo que el código queda completamente acoplado a Spring (e.g. no hay forma de lanzar el test anterior sin emplear SpringJUnit4ClassRunner ).

Interfaz ObectFactory de Spring

Por último, aunque en mi opinión, la forma más limpia de resolver el problema del Bean inyectado con ámbito es utilizar la interfaz ObjectFactory de Spring. Mediante este procedimiento, en lugar de inyectar el Bean en sí mismo, lo que hacemos es inyectar una factoría para el Bean empleando esta interfaz.

En lugar de que el Bean se inyecte durante la instanciación de la clase con ámbito Singleton, se inyecta una instancia de la interfaz ObjectFactory para un tipo concreto de Bean. Dependiendo del tipo y ámbito configurado para ObjectFactory, su método getObject devolverá una instancia del Bean de acuerdo con dicha configuración.

El siguiente test muestra el comportamiento esperado:

El empleo de ObjectFactory no violará ninguno de los principios de la programación orientada a objetos ya que siempre se puede inyectar una implementación específica de ObjectFactory sin depender de Spring ni ningún otro contenedor:

Conclusión

Esta publicación muestra como resolver el problema de los Beans inyectados con ámbito que ocurre cuando se inyecta un Bean con ámbito Prototype dentro de un Bean de ámbito Singleton. Hemos visto las diferentes técnicas para resolver dicho problema y las ventajas y desventajas de cada una de ellas.

El código fuente completo de este artículo puede encontrarse en GitHub.

Dejar un Comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *