Beans condicionales en Spring Boot

Beans Condicionales Spring Boot

Beans Condicionales Spring Boot


En este artículo vamos a seguir analizando como controlar, como ya vimos en un artículo anterior, el ciclo de vida de un Bean através de beans condicionales en Spring Boot.

Cuando construimos una aplicación en Spring Boot, muchas veces vamos a querer hacer un control sobre algún Bean en función de una condición. Para cuando esto ocurre Spring nos proporciona la anotación @Conditional, la cual nos va a permitir crear una condición propia para la creación de ese Bean.

Uso de @Condition en Spring Boot

La ejecución normal de una aplicación en Spring Boot hace que al arrancar el application context se encarge de la gestión de los Beans. Estos Beans en el caso en el que no exista ningún tipo de condición se instanciaran de una manera secuencial.

En los casos en los que añadamos la anotación @Condition, el flujo normal de construcción se verá alterado.

¿Por qué hacer uso de @Condition en Spring Boot?

El uso de @Condition es bastante común en dos procesos, el primero es cuando queremos que algún bean por dependencia de terceros no aparezca en nuestra fase de test; o que nuestra aplicación pueda cargar por ejemplo, una base de datos u otra en función de si la dependencia se encuentra en nuestro classpath.

O quizás en función de en entorno de desarrollo (DEV, PRE, PROD), podemos querer instanciar unos u otros beans, que podremos controlar através de una condición.

¿Cómo declarar Beans con @Condition en Spring Boot?

Cada vez que creeemos un Bean podemos añadir una condición en la que establecemos si queremos que ese Bean pueda ser añadido al Application Context. Vamos a ver como podemos definir estas condiciones.

Los condicionales se pueden añadir tanto a los métodos que definen un @Bean, como a los @Service, @Component, @Repository o @Controller. O a las clases con @Configuration que contienen diversos Beans.

Uso de @ConditionalOnProperty

ConditionalOnProperty nos permite añadir una precondición através de una propiedad, es decir, vamos a crear un bean si una propiedad previamente definida se cumple:

@Configuration
@ConditionalOnProperty(
    value="mongo.enabled", 
    havingValue = "true", 
    matchIfMissing = true)
public class MongoDBEnable{
  
}

Según vemos en el ejemplo anterior todos los Bean que existan dentro de esa clase se van a instanciar sí y solo sí, la propiedad mongo.enabled de nuestro fichero de propiedades se encuentra a true.

En el caso en el que la propiedad no exista también serán instanciados todos los Beans.

Uso de @ConditionalOnExpression con Spring Boot

Otra de las maneras que tenemos de añadir condiciones a nuestros Bean es através de una expresión, por ejemplo.

@Configuration
@ConditionalOnExpression(
    "${mongo.enabled:true} and ${database.enabled:true}"
)
class MongoDBEnabled{
  ...
}

En el ejemplo anterior lo que hemos hecho ha sido crear, en función de una expresión, una condición para instanciar o no, todos los Beans de esa clase.

Para realizar las expresiones se tiene en cuenta el Spring Expression Language.

Uso de @ConditionalOnBean en Spring Boot

En algunas ocasiones durante nuestro desarrollo puede que necesitemos crear un Bean que solo se cree sí y solo sí previamente se ha creado otro Bean. Para ello haremos uso de la anotación @ConditionalOnBean.

@Configuration
@ConditionalOnBean(DataBase.class)
class MongoDBEnabled{
  ...
}

Uso de @ConditionalOnMissingBean en Spring Boot

Esta anotación, nos va a permitir hacer lo mismo que la anterior pero al contrario es decir, en el caso en el que no exista un Bean, nos va a permitir crearlo.

@Configuration
class H2DataBase {

  @Bean
  @ConditionalOnMissingBean
  DataSource dataSource() {
    return new InMemoryDataSource();
  }
}

El caso anterior es un caso muy frecuente, el instanciar una Base de Datos en memoria como H2, sino se encuentra un Bean de tipo DataSource.

Uso de @ConditionalOnResource en Spring Boot

También vamos a poder crear un Bean en función de si existe un determinado recurso por ejemplo un fichero.

@Configuration
@ConditionalOnResource(resources = "/client.txt")
class ClientConnection{
  ...
}

Si el fichero se encuentra en nuestra carpeta resources todos los beans que se encuentren dentro de la clase ClientConnection serán instanciados.

@ConditionalOnClass en Spring Boot

Esta anotación realizará la creación de uno o varios Beans si una determinada clase se encuentra en el classpath:

@Configuration
@ConditionalOnClass(name = "DataSource.class")
class MongoDBConfiguration {
  ...
}

@CondtiionalOnMissingClass uso en Spring Boot

Esta anotación es lo contrario a lo que sería la anotación de @ConditionalOnClass se encargará de hacer la carga de uno o varios Beans en el caso en el que no se encuentre en el classpath.

@Configuration
@ConditionalOnMissingClass(name = "DataSource.class")
class MongoDBConfiguration {
  ...
}

@ConditionalOnJava en Spring Boot

En algunas ocasiones utilizaremos diferentes versiones de Java en nuestro proyecto. Por lo que para estas ocasiones quizás necesitemos instanciar un Bean u otro en función de la versión de Java haciendo uso de @ConditionalOnJava:

@Configuration
@ConditionalOnJava(JavaVersion.TEN)
class ConditionalOnJavaClass {
  ...
}

@ConditionalOnCloudPlatform en Spring Boot

En un mundo cloud y trabajando en arquitectura de microservicios, lo más normal es desplegar nuestros servicios en algún cloud. Es por eso que vamos a hacer uso de la anotación @ConditionalOnCloudPlatform en función del cloud en el que despleguemos. Esta anotación nos permitirá crear uno o varios Bean que dependerá del Cloud desplegado.

@Configuration
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
class ConditionalOnCloud {
  ...
}

@ConditionalOnWebApplication en Spring Boot

Esta anotación se encargará de crear un Bean si se encuentra dentro del WebApplication de la aplicación:

@Configuration
@ConditionalOnWebApplication
class ConditionalOnWeb{
  ...
}

@ConditionalOnNotWebApplication en Spring Boot

Esta anotación hace lo inverso de la anotación anterior, es decir creará un Bean sino se encuentra en el WebApplication.

@Configuration
@ConditionalOnNotWebApplication 
class ConditionalOnNotWeb{
  ...
}

Creación de un Condicional propio en Spring Boot

Aunque Spring Boot nos proporciona un buen número de Condicionales para la creación de nuestros Beans, quizás necesitemos crear un propio condicional que dependa de alguna condición propia. Por ejemplo podremos crear un condicional que se creará en función de si el día de hoy es par o impar:

Para poder crear un Condicional propio en Spring Boot necesitamos implementar la clase Condition.

public class OddConditionDay implements Condition {

  @Override
    public boolean matches(
        ConditionContext context, 
        AnnotatedTypeMetadata metadata) {

  	  return (LocalDate.now().getDayOfMonth() != 0)
    }
}

Con el fragmento de código anterior vamos a devolver si un día del mes es impar, y en función de esta condición crearemos un bean.

@Bean
@Conditional(OddConditionDay.class)
Day dayConditionBean() {
  return new Day();
}

Condicionales anidados con AND en Spring Boot

Spring Boot nos permite combinar condicionales con And únicamente añadiendo los condicionales custom en la mismo clase o añadirlo junto a @Conditional.

Hay que tener en cuenta que Spring Boot no nos permite añadir más de un @Conditional. De todos modos podemos extender de la clase AllNestedConditions  para poder crear un AND con todas las condiciones.

class MongoOrPostgreSql extends AllNestedConditions  {

  MongoOrPostgreSql() {
    super(ConfigurationPhase.REGISTER_BEAN);
  }

  @Conditional(MongoCondition.class)
  static class Mongo {}

  @Conditional(PostgreSql.class)
  static class PostgreSql {}

}

La configuración que añadamos en ConfigurationPhase podrá ser:

  • Para Bean, con REGISTER_BEAN.
  • Para @Configuration con PARSE_CONFIGURATION.

Combinaciones de Condicionales con Or

En el caso en el que necesitemos hacer un OR con diferentes condiciones podemos extender de AnyNestedCondition.

class MongoOrPostgreSql extends AnyNestedCondition {

  MongoOrPostgreSql() {
    super(ConfigurationPhase.REGISTER_BEAN);
  }

  @Conditional(MongoCondition.class)
  static class Mongo {}

  @Conditional(PostgreSql.class)
  static class PostgreSql {}

}

La configuración que añadamos en ConfigurationPhase podrá ser:

  • Para Bean, con REGISTER_BEAN.
  • Para @Configuration con PARSE_CONFIGURATION.

Condicionales propios con NOT

Si lo que buscamos es crear un condicional que haga una negación en lugar de un AND o un OR, al estilo de ConditionalOnMissingBean entonces tendríamos que extender de NoneNestedCondition.

class MongoOrPostgreSql extends NoneNestedCondition{

  MongoOrPostgreSql() {
    super(ConfigurationPhase.REGISTER_BEAN);
  }

  @Conditional(MongoCondition.class)
  static class Mongo {}

  @Conditional(PostgreSql.class)
  static class PostgreSql {}

}

Conclusión

Cuando creamos una aplicación en la que tenemos diferentes entornos o creación de Beans en función de alguna casuística es muy importante saber aplicar beans condicionales en Spring Boot. En este artículo hemos visto tanto los que nos proporciona Spring Boot, como la manera en crear uno propio.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!


Deja una respuesta

Tu dirección de correo electrónico no será publicada.