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
Thanks
...Gopi
www.allibilli.com
Bill from the US
Thank you for the blog and it is helpful. I am wondering how would you do debugging for OSGI bundles ?
Thank you,
Sri
Debugging an OSGi bunddle is no different than regular java applications. Do you have any specific requirement?