Spring 3 – Environment/Profile based bean initialization and configuration


As we all know spring team is great and they always do wonder things. No different this time with spring 3.1 M1 release as well…

In this post I am going to cover an interesting bean configuration/initialization strategy using which we can control bean initialization based on environment/profile. Spring team has introduce a concept of Environment and Profile using which we can annotate bean(s) (or mark them in XML) so that those beans will be initialized based on environment and active profile.

Consider environment being a wrapper around our application context that know (or has information) about the environment in which application is running. Profile is something using which we can group various beans so that those beans will only be initialized if a particular profile is enabled in an environment.

For example we have a simple application that uses some kind of third part service to validate the credit card numbers. Assume that the service charges us on per transaction basis and the only thing that is returned by service is a flag (or some transaction ID) based on which our application makes a decision about whether we should continue with processing or not? Also the credit card validation service is secured and it cannot be accessed from developer machine. So we have following limitation:
1) The credit card validation service is a paid service and we need to pay extra amount if we use the same service for development purpose.
2) Since the service is only accessible in production environment therefore either we need to test our application only when it is deployed on production or allow developers/QAs to access the production environment for development/testing purpose (which is not a good idea at all).
3) Rebuild the application for DEV/QA and production environment by making the code changes every time before doing a build (pass hardcoded values or skip the credit card validation in DEV/QA)

Does this sounds good? NO! So, what is the solution?

As we have assumed that we our credit card service just returns a flag (or transaction ID) based on which application will continue/stop processing so, what we can do is we can write a dummy credit card validation service (and create a bean) for it that will be initialized (and injected) in DEV/QA environment and in PROD environment actual bean be will be initialized/injected. Here is sample configuration file:




 
  
            
 
  
  
     
              
     
  
  
  
  
     
           
     
    


This same thing can be achieved using class level annotations as well, here is an example:
A) Our “creditCardManager” bean that is common for all
@Configuration
public class ApplicationConfiguration {

 @Bean
 public CardManager creditCardManager(){
  return new CreditCardManager(creditCardService());
 }
 
}

public interface CardManager {
 public boolean validateCard(String cardNumber, String expDate, String cvvNumber);
}

public class CreditCardManager implements CardManager {
 private CreditCardService creditCardService;
 
 public CreditCardManager(CreditCardService creditCardService){
  this.creditCardService = creditCardService;
 }

 public CreditCardService getCreditCardService() {
  return creditCardService;
 }

 public void setCreditCardService(CreditCardService creditCardService) {
  this.creditCardService = creditCardService;
 }

 @Override
 public boolean validateCard(String cardNumber, String expDate, String cvvNumber) {
  // Add some logic here
  return true;
 }
}
B) “creditCardService” service bean that will be initialized/injected in to “creditCardManager” when “prod” profile is enabled
/**
Production configuration/beans that will be initialized in when "prod" profile is enabled
**/
@Configuration
@Profile(value="prod")
public class ProdCreditCardConfiguration {
 @Bean
 public CreditCardService creditCardService(){
  return new CreditCardService(dataSource());
 }
 
 @Bean
 public DataSource dataSource(){
  return new BankDataSource();
 }
}

public class CreditCardService {
 private DataSource dataSource;
 
 public CreditCardService(DataSource dataSource) {
  this.dataSource = dataSource;
 }

 public DataSource getDataSource() {
  return dataSource;
 }

 public void setDataSource(DataSource dataSource) {
  this.dataSource = dataSource;
 }
 
}
C) “creditCardService” service bean that will be initialized/injected in to “creditCardManager” when “dev” profile is enabled
/**
Dummy configuration/beans that will be initialized in when "dev" profile is enabled
**/
@Configuration
@Profile(value="dev")
public class DummyCreditCardConfiguration {

 @Bean
 public CreditCardManager creditCardManager(){
  return new CreditCardManager(creditCardService());
 }
 
 @Bean
 public DummyCreditCardService creditCardService(){
  return new DummyCreditCardService(dataSource());
 }
 
 @Bean
 public DataSource dataSource(){
  return new DummyBankDataSource();
 }
}

public class DummyCreditCardService extends CreditCardService{

 public DummyCreditCardService(DataSource dataSource) {
  super(dataSource);
 }

}
Now the question is how to enable a particular profile in various environments. There are various ways to do this:
1) Programmatically
 GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
 // To set multiple profiles use "," separated profile names e.g. ctx.getEnvironment().setActiveProfiles("dev, qa, prod");
 ctx.getEnvironment().setActiveProfiles("dev"); 
 ctx.load("classpath:/com/bank/config/xml/*-config.xml");
 ctx.refresh();
2) System environment properties (Based on the operating system the processing of setting environment property will be different). We need to set “spring.profiles.active” value to “dev” or “prod” based on the profile that we want to activate.
3) JVM system properties
We can also set the JVM system properties using -Dspring.profiles.active="dev" or -Dspring.profiles.active="prod". To set multiple profiles use "," separated profile names -Dspring.profiles.active="dev, qa"
4) Servlet context parameters using web.xml
 
  dispatcher
  org.springframework.web.servlet.DispatcherServlet
  
   spring.profiles.active   dev  
 
Also, if you want to configure application in such a way that if no profile is active a default profile should come in to play then you can use “spring.profiles.default” property to set a default profile. With these configurations in place we don’t need to change the code or rebuild the application, based on the environment appropriate beans will be initialized/injected to the “creditCardManager” bean.
This is just a simple example but this can be very useful in cases where we have multiple environment  and based on environment want different set of services (beans) to be initialized.
Further reading:

Comments

AlliBilli said…
Thanks for this post. While I am searching for this and I found this saves lot of my time.

Thanks
...Gopi
www.allibilli.com
Anonymous said…
Thanks, man. This and the links were especially helpful.

Bill from the US
sreeni said…
Hi Surya,

Thank you for the blog and it is helpful. I am wondering how would you do debugging for OSGI bundles ?

Thank you,
Sri
Suryakand said…
@Sri
Debugging an OSGi bunddle is no different than regular java applications. Do you have any specific requirement?

Popular posts from this blog

AEM, FORM Submission & Handling POST requests

AEM - Query list of components and templates

AEM 6.3 - Bundle Whitelisting - Deprecation of administrative authentication

AEM as a Cloud Service (AEMaaCS) – Architecture Overview