I have gotten some positive feedbacks on the project, which I very much appreciate, and found myself in need for a place where people could read documentation and files issues. So I have created a redmine instance on my machine where you could do just that. http://projects.kaare-nilsen.com/projects/show/1
One important thing to mention is that I have changed from using Subversion to using mercurial as source control management, and the new url for the source code is now https://hg.kaare-nilsen.com/staged-spring
I am happy to announce the release of the AspectJ plugin version 1.0 for Maven 2.
This release provides:
For instructions on how to use the plugin, refer to it’s web site at: http://mojo.codehaus.org/aspectj-maven-plugin
As we have seen in the previous post with the @Stage annotation or the StagedSingletonFactoryBean, you can by using them choose between different implementation of Spring bean at runtime. But sometimes it is not the implementation you would like to switch between but the internal configuration of a particular class.
To support this, we have the StagedProperties class. It is a class in where you can register multiple property files used for looking up staged values for a given property key using stage as a prefix to separate values.
Property file layoutEach property file may have several entries for one key each prefixed by the stage it should apply. If no value found for the stage we are running in, it falls back on the key with no prefix. As shown in the following example
#
# myApp properties
#
myKey=default value
development.myKey=development value
test.myKey=test value
production.myKey=production value
custom.myKey=custom value
You are also allowed to use keys in the values, and it will be resolved and replaced with the value of the corresponding key as shown here :
#
# myApp properties
#
baseUrl=http://localhost
development.baseUrl=http://dev.arktekk.no
test.baseUrl=http://test.arktekk.no
production.baseUrl=http://arktekk.no
customerServiceEndpoint=${baseUrl}/services/customerService
orderServiceEndpoint=${baseUrl}/services/mockCustomerService
StagedProperties properties
= new StagedProperties("myProperties.properties");
String myValue = properties.getProperty("myKey");
There are also constructors taking a list of property files, or you may use the registerPropertiesmethod to register more files. The StagedProperties class uses Spring resource loader to load the property files so you can use all the Spring supported urls, as e.g. classpath:myProperties.properties.
In my opinion I do like spring beans that tries to configure them selves as much as possible. I think there are way to much use of Spring configuration files to configure ordinary properties like database url’s, service end points and so on. So what I would prefer is that we configure a StagedProperties bean in Spring, and then use the Spring autowiring support to get it injected unto other spring beans, and then use the InitializingBean inteface or the new support for the @PostConstruct annotation In this way we only need to configure our spring beans, in the case that the default values does not work in our application. This is best explained in the 2 part example below.
Configuring the StagedProperties beanFirst we need to configure our properties in Spring by adding this xml snippet
<bean id="stagedProperties"
class="no.arktekk.stagedspring.properties.StagedProperties">
<property name="propertyFiles">
<list>
<value>no/arktekk/stagedspring/properties/test1.properties</value>
</list>
</property>
</bean>
Then it is time to get it injected, and utilized in a spring bean
public class SpringBean {
@Autowired private StagedProperties properties;
@PostConstruct
public void initProperties() throws Exception {
band = properties.getProperty("aCoolBand", "Mothers of invention");
}
Note by using afterPropertiesSet or @PostConstruct this will override any configuration done in Spring configuration files, and the only way to change the values is by using the property files
StagedProperties uses an ArrayList internally and, in the case of multiple files are registered, you are allowed to use a key in several files. When the StagedProperties class encounters several keys that match, it will always use the key registered last in the class. You may also register a file that does not yet exists, and it will be picked by the refresh mechanism.
Refreshing of property filesWhen a StagedProperties class is added to a Spring context, it will start a refresh thread, that runs on the schedule defined in the property refreshIntervalInSecods. The default here is 60 seconds. If you are constructing the class yourself you will need to start the refresh mechanism manually by invoking the startRefreshingCache method.
Configuring Spring beans with placeholdersIn addition, those of you familiar with Spring probably know the PropertyPlaceHolderConfigurer that Spring ships with. To be able to use this kind of configuration and use stages this project provide a StagedPropertyPlaceHolderConfigurer class that are able to use the StagedProperties class to resolve the values. This functionality is best shown with an example
<bean id="stagedProperties"
class="no.arktekk.stagedspring.properties.StagedProperties">
<property name="propertyFiles">
<list>
<value>myProperties.properties</value>
</list>
</property>
</bean>
<bean id="placeHolderConfigurer"
class="no.arktekk.stagedspring.properties.StagedPropertyPlaceholderConfigurer">
<property name="stagedProperties" ref="stagedProperties" />
</bean>
<bean id="testBean" class="..TestBean">
<property name="url" value="${aCoolUrl}" />
</bean>
of course the stagedProperties property is marked with the @Autowired annotation so the ref shown above is optional
Please check out the latest version of the code at staged-spring and run the mvn site:site command for more detailed documentation.
The Spring framework has in our parts of the world become the kind of de-facto standard for almost all enterprise Java pplications. There are close to zero projects starting up in the Oslo area not using it. In my role as consultant and Spring Instructor I have over the years seen my share of Spring based projects, and one of the discussions that I see in these
projects are how should we configure our Spring applications ?
Well of course there are no one answer to that question, and the options we have when we use Spring are many, and sufficient for most use cases. But there is one use case that too often have been a kind of pain in the….
How do we manage configurations in various stages of development?
Let us say that we are going to use one implementation of a interface when we are developing our application, and another one in production. Let us say for e.g. mocking purposes. How should we configure our application to support that.
There is always the option of having your build system do it for you with ant filters, or Maven resource filtering. But hey. That often require us to build the application one time for each environment.
Then we have PropertyPlaceholderConfigurer. It is a very little know fact, that it also can be used to select bean classes using property files. But again. We would need one property file for each stage, and it can be somewhat difficult to make sure the correct one is used.
Also we have the option of having more bean xml files. one for tests, one for development and then include the right one, but that gives us the hassle of keeping them in sync.
I have also tried using special factory beans to solve this, but that gave me quite large xml files.
And there is another common complaint
There are way to much xml when using Spring!
In Spring 2.5 they introduced autodetection of components in the classpath by scanning for annotated classes. The annotation used for detecting components are called stereotypes, and Spring ships with the follwing out of the box :
What the component scanner does, is looking for these stereotypes, and registers the classes as beans in the running Spring container. By default it uses the class name as bean id e.g. the class DevelopmentEnvironmentService annotated with the @Service annotation would be registered as a bean with the id developmentEnvironmentService :
@Service public class DevelopmentEnvironmentService implements EnvironmentService { public String getEnvironment() { return “development”; } }Well all good so far. But how to get a hold of this bean, and still keep us free from xml configuration files ?
Let us say we have a service that is supposed to use the EnvironmentService interface to query for the environment we are running in. First we need to create the ConfigurationService and let it be known to the container with the @Service annotation.
@Service public class ConfigurationService { }Then we need to inject it with an instance of the EnvironmentService. To do this, we use the @Autowired annotation. And after we have provided the dependency and implemented the business call the class may now looks like this :
@Service public class ConfigurationService { @Autowired EnvironmentService environmentService; public String getEnvironment() { return “development”; } }That’s it. basically all you need to wire up spring applications using annotations instead of xml. ehh. well almost. You may want to add this little snippet to your xml configuration file :
<beans xmlns=“http://www.springframework.org/schema/beans” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:context=“http://www.springframework.org/schema/context” xsi:schemaLocation= “http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd”> <context:component-scan base-package=“no.arktekk.stagedspring”/> </beans>With that said, there are other options like JavaConfig, or by creating your application context in Java, setting the correct scanners in code, but hey. the above snippet is kind a good enough for me.
Half way thereOk now we have taken the xml configuration files out of the equation, but what happens if we have 2 instances of the EnvironmentService interface in the container ?
Well I can tell you… you get this stacktrace :
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'configurationService': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: no.arktekk.stagedspring.service.EnvironmentService no.arktekk.stagedspring.service.ConfigurationService.environmentService; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [no.arktekk.stagedspring.service.EnvironmentService] is defined: expected single matching bean but found 2: [developmentEnvironmentService, productionenvironmentService] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:240) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:876) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:437) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:383) at java.security.AccessController.doPrivileged(Native Method) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:353) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:245) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:169) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:242) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:400) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:736) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:369) at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:123) at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:66) at no.arktekk.stagedspring.context.StagedClassPathXmlApplicationContext.(StagedClassPathXmlApplicationContext.java:37) at no.arktekk.stagedspring.StageTest.defaultStageResolve(StageTest.java:38) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59) at org.junit.internal.runners.MethodRoadie.runTestMethod(MethodRoadie.java:98) at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:79) at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:87) at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77) at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42) at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMethod(JUnit4ClassRunner.java:88) at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51) at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44) at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27) at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37) at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196) Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: no.arktekk.stagedspring.service.EnvironmentService no.arktekk.stagedspring.service.ConfigurationService.environmentService; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [no.arktekk.stagedspring.service.EnvironmentService] is defined: expected single matching bean but found 2: [developmentEnvironmentService, productionenvironmentService] at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:433) at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:79) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:237) … 38 more Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [no.arktekk.stagedspring.service.EnvironmentService] is defined: expected single matching bean but found 2: [developmentEnvironmentService, productionenvironmentService] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:583) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:409) … 40 moreWhat have I done to end up here ?
What I did is that I added this class below to my project :
And then, suddenly I am back to square one. I know this is somewhat a strange and constructed example, but is has been quite common in projects I have been in, that we need different implementations of an interface. Most often for mocking purposes. So
what I think Spring need to have support for is to have a discriminator between stages. And in true Open Source style, I thought.. well if they do not add it. then I have to do it
So what I did was to create a little addon to the Spring framework providing the neccecary hooks to make this happen. it consists of just a few classes, and one annotation.
@Stage annotationFirst, the @Stage annotation. This annotation serves as the discriminator, and can be registered on types. It has a String value, and defines 3 default stages :
When this annotation is present on a Java class, it tells the new stage enabled bean factory which stage this particular class should apply to.
StagedListableBeanFactoryThis is a spring bean factory that when autowiring is happening, finds all the candidate classes, checks if they have a @Stage annotation and if there are more than one candidate, select the one that have the correct stage set and gives the reference back to the running application context.
Application ContextThere are 2 application contexts supplied in the 1.0 version of Staged Enabled Spring Contexts StagedClassPathXmlContext and StagedXmlWebApplicationContext. To enable stage detection in spring you will have to use one of these contexts
StagedClassPathXmlContextUsed when a set of xml based configuration file is used to assemble a context in Java code. e.g. for embedding Spring in a server application, or test cases :
@Test public void defaultStageResolve() { ApplicationContext ctx = new StagedClassPathXmlApplicationContext(“classpath:applicationContext.xml”); ConfigurationService configurationService = (ConfigurationService) ctx.getBean(“configurationService”); assertEquals(Stage.PRODUCTION, configurationService.getRunningEnvironment()); } StagedXmlWebApplicationContextUsed when you are creating a web application. To enable this context instead of the default spring context, you will need to set a context param in you web application web.xml file :
<context-param> <param-name>contextClass</param-name> <param-value>no.arktekk.stagedspring.context.StagedXmlWebApplicationContext</param-value> </context-param>Finally, we need to know what environment we are actually running in, and that is done with a configurable StageResolver strategy.
StageResolverThis is an interface that you could use to tell the stage enabled what environment we are running in. A default strategy is supplied, and it uses simply a system property to resolve the environment, defaulting to Stage.PRODUCTION. Of course there is nothing keeping you to supply your own environment check, by simply implementing that interface and register it as a bean in the context. The bean factory will automatically choose the one supplied by you if found.
Putting it all togetherThen I have shown how to use the Stage Enabled Spring Contexts project, and it is time to summarize a little :
Stage Enabled Spring Contexts is currently only available from my own public Subversion repository at :
http://kaare-nilsen.com/subversion/public/labs/staged-spring
The short answer. Nope. But for a 1.0 release I think it is sufficient. What I would like to point out is that it currently does not work well with the @Qualifier annotation, so if you are using it, the stage may not be correctly resolved.
I aim to fix that in a 1.1 release in a short while.
So as the last ting I would say, is that I hope you find it interesting and please drop me questions and suggestions to make it even better.