Definiciones condicionales en Spring
Cuando se utiliza Spring para desarrollar aplicaciones, muchas veces necesitamos cambiar la definición de algún bean en función de una variante. El caso más común que nos podemos encontrar consiste en la definición del dataSource de la BBDD. Como es lógico, la aplicación en producción hará uso de una BBDD Oracle, MySQL o similar, mientras que los tests de integración usarán una BBDD en memoria. ¿Qué alternativas da Spring para hacer esto?
Java config
Una primera aproximación consistiría en utilizar la configuración usando clases Java para determinar el bean que se desea definir.
@Configuration public static class ContextConfig { @Bean public DataSource dataSource() { if (System.getProperty("production") != null) { return getUsingLookup(); } return getEmbeddedDataSource(); } }
@Profile
Antes de Spring 4, también se podía utilizar los denominados profiles. De forma resumida, un profile engloba los beans definidos en él, de modo que esas definiciones sólo se utilizarán en la aplicación si se activa dicho perfil. ¿Cómo podemos definir los perfiles? Pues, como ya Spring nos tiene acostumbrados, existen varias formas:
En XML: Se puede asociar el atributo profile. Por ejemplo:
<beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/extrema-sistemas/schema.sql"/> <jdbc:script location="classpath:com/extrema-sistemas/test-data.sql"/> </jdbc:embedded-database> </beans> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans>
Configuración Java: Usando la anotación @Profile
@Configuration @Profile("dev") public class DevConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/extrema-sistemas/schema.sql") .addScript("classpath:com/extrema-sistemas/test-data.sql") .build(); } } @Configuration @Profile("production") public class ProductionConfig { @Bean public DataSource dataSource() { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
Por último, sólo quedaría especificar el perfil que deseamos utilizar, que cambia dependiendo del entorno. Por ejemplo, en tests de integración se utilizaría la anotación @ActiveProfiles(profiles="dev"), mientras que en una aplicación en producción puede utilizarse java -Dspring.profiles.active=production, entre otras formas.
@Conditional
En diciembre, una de las características con las que nos sorprendió Spring 4 fue la inclusión de la anotación @Conditional, con la que el framework permite al usuario especificar con mayor detalle si una definición (o conjunto de definiciones) debe incluirse en el ApplicationContext. ¿Cómo? Veámoslo creando nuestra propia anotación @Profile
Primero, creamos una anotación a la que se le especificará el perfil para el que se quiere activar
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Conditional(MyProfileCondition.class) public @interface MyProfile { public String value(); }
Luego, creamos la clase que implementará la lógica de la condición:
public class MyProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // recuperar el valor de la anotación MyProfile y aplicar la lógica correspondiente } }
Por último, utilizamos la configuración Java:
@Bean @MyProfile("production") public DataSource dataSource1() { return getUsingLookup(); } @Bean @MyProfile("dev") public DataSource dataSource2() { return getEmbeddedDataSource(); }
Obtenemos así un ejemplo sencillo de cómo podemos crear condiciones más complejas a la hora de añadir definiciones al ApplicationContext gracias a una de las nuevas características de Spring 4.
Para terminar, cabe mencionar que en esta nueva versión de Spring, la funcionalidad ofrecida con @Profile está implementada haciendo uso de @Conditional y su correspondiente clase ProfileCondition.java, tal y como pueden observar aquí










