This Spring tutorial is to learn about @Conditional annotation that was introduced in Spring 4.0 We shall see about what @Conditional annotation is, in what scenarios it can be used, difference between @Conditional vs @Profile annotations and an example Spring application using @Conditional
annotation.
We need to have some basic knowledge of the following concepts before leaning @Conditional annotation.
Please go through “Spring @Profile Annotation” topic to get knowledge on Spring’s Java-based Configuration and Spring Profiles.
Spring 4.0 has introduced a new annotation @Conditional
. It is used to develop an “If-Then-Else” type of conditional checking for bean registration.
Till Spring 3.x Framework something similar to @Conditional annotation are,
Spring 4.0 @Conditional annotation is at more higher level when compared to @Profile
annotation. @Profile annotation should be used for loading application configuration based on conditional logic.
@Profile annotation is restricted to write conditional checking based on predefined properties. @Conditional annotation does not have this restriction.
Both Spring @Profiles and @Conditional annotations are used to develop an “If-Then-Else” conditional checking. However, Spring 4 @Conditional is more generalized version of @Profile annotation.
Spring Boot module makes heavy use of @Conditional
annotation.
We can use Spring @Conditional annotation for the following scenarios:
This list is just a set of examples.
We need to have a class implementing an interface named Condition provided by Spring. It has a method matches and our conditional logic should go inside this method. Then we can use the class we have defined in the @Conditional
annotation to check for a condition. Following the Condition interface that we need to implement in our custom condition class.
public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
In this example let us see how we can use @Conditional annotation to check a property value is dev or prod from environment. For learning we will use Java based configurations in this example.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javapapers.spring4</groupId> <artifactId>Spring4.0Conditional</artifactId> <version>1.0.0</version> <properties> <spring-framework.version>4.0.9.RELEASE</spring-framework.version> <junit.version>4.11</junit.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-framework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> </dependencies> </project>
package com.javapapers.spring4.domain; public class Employee { private int id; private String name; private double sal; public Employee(int id,String name,double sal) { this.id = id; this.name = name; this.sal = sal; } public int getId() { return id; } public String getName() { return name; } public double getSal() { return sal; } }
Create Utility classes to represent in-memory Databases. For simplicity, we are not interacting with Databases and also are using our DataSource interface rather than javax.sql.DataSource. DevDatabaseUtil
class represents DEV database and ProductionDatabaseUtil
class represents PROD database with some data.
package com.javapapers.spring4.util; import java.util.List; import com.javapapers.spring4.domain.Employee; public interface DataSource { List<Employee> getEmployeeDetails(); }
package com.javapapers.spring4.util; import java.util.ArrayList; import java.util.List; import com.javapapers.spring4.domain.Employee; public class DevDatabaseUtil implements DataSource { @Override public List<Employee> getEmployeeDetails(){ List<Employee> empDetails = new ArrayList<>(); Employee emp1 = new Employee(111,"Abc",11000); Employee emp2 = new Employee(222,"Xyz",22000); empDetails.add(emp1); empDetails.add(emp2); return empDetails; } }
package com.javapapers.spring4.util; import java.util.ArrayList; import java.util.List; import com.javapapers.spring4.domain.Employee; public class ProductionDatabaseUtil implements DataSource { @Override public List<Employee> getEmployeeDetails(){ List<Employee> empDetails = new ArrayList<>(); Employee emp1 = new Employee(9001,"Ramu",45000); Employee emp2 = new Employee(9002,"Charan",35000); Employee emp3 = new Employee(9003,"Joe",55000); empDetails.add(emp1); empDetails.add(emp2); empDetails.add(emp3); return empDetails; } }
Create a EmployeeDAO class to represent DAO Layer.
package com.javapapers.spring4.dao; import java.util.List; import com.javapapers.spring4.domain.Employee; import com.javapapers.spring4.util.DataSource; public class EmployeeDAO { private DataSource dataSource; public EmployeeDAO(DataSource dataSource) { this.dataSource = dataSource; } public List<Employee> getEmployeeDetails() { return dataSource.getEmployeeDetails(); } }
Create a EmployeeService class to represent Service layer. It interacts with DAO Layer class to get Employee Details data.
package com.javapapers.spring4.service; import java.util.List; import com.javapapers.spring4.dao.EmployeeDAO; import com.javapapers.spring4.domain.Employee; public class EmployeeService { private EmployeeDAO employeeDAO; public EmployeeService(EmployeeDAO employeeDAO) { this.employeeDAO = employeeDAO; } public List<Employee> getEmployeeDetails(){ return employeeDAO.getEmployeeDetails(); } }
Now it’s time to implement Spring 4 Conditional checking by using @Conditional
annotation. Both condition checking classes implements Spring’s Condition interface matches() method. Both Conditional classes checks for “database.name” from Environement. If this values is “dev”, then DevDataSourceCondition’s matches() method returns true. Otherwise false. In the same way, if this property value is “prod”, then ProdDataSourceCondition’s matches() method returns true. Otherwise false.
package com.javapapers.spring4.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; public class DevDataSourceCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String dbname = context.getEnvironment().getProperty("database.name"); return dbname.equalsIgnoreCase("dev"); } }
package com.javapapers.spring4.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; public class ProdDataSourceCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String dbname = context.getEnvironment().getProperty("database.name"); return dbname.equalsIgnoreCase("prod"); } }
getDevDataSource() defines @Conditional annotation with DevDataSourceCondition class. That means if DevDataSourceCondition’s matches() method returns true, then EmployeeDataSourceConfig creates DataSource object to refer to DEV Database.
In the same way, getProdDataSource() defines @Conditional annotation with ProdDataSourceCondition class. That means if ProdDataSourceCondition’s matches() method returns true, then EmployeeDataSourceConfig creates DataSource object to refer to PRDO Database.
EmployeeConfig class configures other beans except DataSource related beans.
package com.javapapers.spring4.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.javapapers.spring4.dao.EmployeeDAO; import com.javapapers.spring4.service.EmployeeService; import com.javapapers.spring4.util.DataSource; @Configuration public class EmployeeConfig{ @Autowired private DataSource dataSource; @Bean public EmployeeService employeeService() { return new EmployeeService(employeeDAO()); } @Bean public EmployeeDAO employeeDAO() { System.out.println("EmployeeDAO employeeDAO()" + dataSource); return new EmployeeDAO(dataSource); } }
package com.javapapers.spring4.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import com.javapapers.spring4.condition.DevDataSourceCondition; import com.javapapers.spring4.condition.ProdDataSourceCondition; import com.javapapers.spring4.util.DataSource; import com.javapapers.spring4.util.DevDatabaseUtil; import com.javapapers.spring4.util.ProductionDatabaseUtil; @Configuration public class EmployeeDataSourceConfig { @Bean(name="dataSource") @Conditional(value=DevDataSourceCondition.class) public DataSource getDevDataSource() { return new DevDatabaseUtil(); } @Bean(name="dataSource") @Conditional(ProdDataSourceCondition.class) public DataSource getProdDataSource() { return new ProductionDatabaseUtil(); } }
Now it’s time to write JUnits to test Spring’s @Conditional classes.
import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import com.javapapers.spring4.config.EmployeeConfig; import com.javapapers.spring4.config.EmployeeDataSourceConfig; import com.javapapers.spring4.domain.Employee; import com.javapapers.spring4.service.EmployeeService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes= {EmployeeConfig.class,EmployeeDataSourceConfig.class},loader=AnnotationConfigContextLoader.class) public class Spring4DevConditionalTest { @Autowired private ApplicationContext applicationContext; @Test public void testDevDataSource() { EmployeeService service = (EmployeeService)applicationContext.getBean("employeeService"); assertNotNull(service); List<Employee> employeeDetails = service.getEmployeeDetails(); assertEquals(2, employeeDetails.size()); assertEquals("Abc", employeeDetails.get(0).getName()); assertEquals("Xyz", employeeDetails.get(1).getName()); } }
import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import com.javapapers.spring4.config.EmployeeConfig; import com.javapapers.spring4.config.EmployeeDataSourceConfig; import com.javapapers.spring4.domain.Employee; import com.javapapers.spring4.service.EmployeeService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes= {EmployeeConfig.class,EmployeeDataSourceConfig.class},loader=AnnotationConfigContextLoader.class) public class Spring4ProdConditionalTest { @Autowired private ApplicationContext applicationContext; @Test public void testProdDataSource() { EmployeeService service = (EmployeeService)applicationContext.getBean("employeeService"); assertNotNull(service); List<Employee> employeeDetails = service.getEmployeeDetails(); assertEquals(3, employeeDetails.size()); assertEquals("Ramu", employeeDetails.get(0).getName()); assertEquals("Charan", employeeDetails.get(1).getName()); assertEquals("Joe", employeeDetails.get(2).getName()); } }
To run this class in Eclipse or Spring STS IDE, please set some Junit Run Configuration as shown below -Ddatabase.name=dev
Similarly you can do for “prod”.
In this example, we will write a Spring’s @Conditional checking based on whether a Bean object is present or not in Spring Application Context.
We are going to use same Example-1 project files with some minor changes.
Create a new Spring 4 Condition to check a bean is available in Application Context or not. If bean is available, BeanPresennceCondition’s matches() method return true. Otherwise, it throws NoSuchBeanDefinitionException exception. Just for testing purpose, use a catch block to handle this exception and continue the control flow to end of the matches() method and return false as shown below.
package com.javapapers.spring4.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; import com.javapapers.spring4.config.EmployeeBeanConfig; import org.springframework.beans.factory.NoSuchBeanDefinitionException; public class BeanPresennceCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { EmployeeBeanConfig employeeBeanConfig = null; try { employeeBeanConfig = (EmployeeBeanConfig)context.getBeanFactory().getBean("employeeBeanConfig"); }catch(NoSuchBeanDefinitionException ex) { } return employeeBeanConfig != null; } }
Create a new Java-based Configuration class. It defines an Employee bean with some data. This bean configuration is using BeanPresennceCondition. If this condition returns true, then only it configures this bean. Otherwise no bean is created.
package com.javapapers.spring4.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import com.javapapers.spring4.condition.BeanPresennceCondition; import com.javapapers.spring4.domain.Employee; @Configuration public class EmployeeBeanConfig{ @Bean @Conditional(BeanPresennceCondition.class) public Employee employee() { return new Employee(222,"Popeye",55000); } }
Create Junit test class to test this. Here we are trying to test “employee” bean data. If BeanPresennceCondition returns true, then only this bean is created.
import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import com.javapapers.spring4.config.EmployeeBeanConfig; import com.javapapers.spring4.domain.Employee; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes= {EmployeeBeanConfig.class},loader=AnnotationConfigContextLoader.class) public class Spring4DevBeanPresenceConditionalTest { @Autowired private ApplicationContext applicationContext; @Test public void testDevDataSource() { Employee emp = (Employee)applicationContext.getBean("employee"); assertNotNull(emp); assertEquals(222, emp.getId()); assertEquals("Popeye", emp.getName()); } }
Now change BeanPresennceCondition
class matches() method logic as show below to check the failure case of the Condition.
package com.javapapers.spring4.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; import com.javapapers.spring4.config.EmployeeBeanConfig; import org.springframework.beans.factory.NoSuchBeanDefinitionException; public class BeanPresennceCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { EmployeeBeanConfig employeeBeanConfig = null; try { employeeBeanConfig = (EmployeeBeanConfig)context.getBeanFactory().getBean("employeeBeanConfig2"); }catch(NoSuchBeanDefinitionException ex) { } return employeeBeanConfig != null; } }
Here we are checking for “employeeBeanConfig2” bean instead of “employeeBeanConfig”. It will definitely fail because our application does not have this bean configuration. When BeanPresennceCondition fails, it returns false value to EmployeeBeanConfig’s employee() method where we are using @Conditional annotation. When this condition fails (that is ELSE block in “IF-ELSE” conditional checking), Spring IOC Container does not load “employee” bean into Spring Application Context.
When we run our Junit now, we will get Junit RED bar showing that failed status with the following error. org.springframework. beans.factory.NoSuchBeanDefinitionException: No bean named 'employee' is defined
.
Download the example Project Spring4.0Conditional
In the previous tutorial on Spring, we learnt about @Profile annotation. Now let us see how @Profile annotation is updated using @Conditional annotation.
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE }) @Documented public @interface Profile { String[] value(); }
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Conditional(ProfileCondition.class) public @interface Profile { String[] value(); }
If we observe both declaration, we can find the following changes to @Profile definition.
Comments are closed for "Spring @Conditional Annotation".
Easy to understand through your example.