Many of us have integrated our application with the wonderful Spring Framework over the years. This may have happened to you in the form of migrating an existing application to Spring, replacing an IoC container, or even starting a new application with Spring incorporated. The steps involved in each of these different scenarios, while wildly different, share the same common trait: figuring out the exact layout of your application, abstracting the contracts, and relying on the dependency injection container to glue the pieces back together.
During the course of this rethinking process our application shrinks, in terms of the lines of code, since at least instantiation and dependency injection have now been delegated to an automated process that does not require any coding beyond some declarations. Using Spring has become so natural to some of us that we do not even think about it, anymore.
As a software engineer, that is my red alert. Whenever you start reusing a technology without reassessing it, you should second guess yourself. Does the value it brought to table two years ago still stand? Are you still using it right? Do you need a refresher to keep up with the changes the technology (and its periphery) has undergone?
Since its beginnings, Spring has been an ever-evolving framework; it has always tried to adhere to the latest paradigms and shift with the changes, while not losing its essence. One of the many changes Spring has incorporated has been the ever-so-controversial move from XML-driven configuration to annotation-driven configuration. We are now empowered with the tools provided to us by the platform itself, wherever possible, so as to avoid long startup times tied with XML file parsing and processing. I do have to mention that it is not technically a move from XML config to annotation config, since you can still write XML configuration files and import them in your project. Or even ignore the Java config capabilities altogether. This has become increasingly difficult, though, as the Spring team is now heavily advocating the newer, better supported Java configuration, specially since the advent of Spring Boot. I do not blame them. I, myself, am a fan of doing annotation configuration. I guess you could say that I have never been much of an XML person.
The problem, however, is that many of us have forgotten why Spring came with an XML configuration file in the first place: to separate the framework from the main project. It was so that we could technically go in another direction if we needed, by changing peripheral code, rather than doing code changes in the main project. Spring configuration was exactly that: a configuration file that left no footprint in our application, except the fact that we now designed with IoC in mind.
In this article, I want to examine the problem of migrating from an older Spring-based application to an annotation-configuration Spring application. Since my discussion here involves practical uses of the framework, I would like to present a couple of sample cases before moving forward. I will then proceed to present the steps involved in making the transition and, finally, conclude by giving a clear recipe for the migration.
Sample: Bean Configuration
One important asset when using XML application configurations is that it helps you understand what happens to a bean during its lifecycle, from its instantiation to the various configurations it undergoes.
Suppose that we want to configure a bean after all of its dependencies have been set. One way to do this is to implement the InitializingBean interface provided by Spring.
Consider the following scenario:
public class MyClass implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { //do some initialization } }
Do you see the Spring code creeping in your application code? Spring provided the much better alternative in its XML API:
<bean class="com.domain.project.component.MyClass" init-method="init" />
Now you could say that your application was not dependent on Spring, and you would be right: you have zero dependency on Spring to initialize your bean, and your only assumption is that somebody will call that init method sometime after the bean’s properties have been set.
In the post-annotation world, this would be how we write it:
public class MyClass { @PostConstruct public void init() { //do some initialization here } }
As you can see, we have moved the declaration from the configuration file over to our application class in the form of the @PostConstruct annotation.
This is bad for multiple reasons:
- You have brought configuration to your code, meaning that now there is no source of truth. Not only that, you cannot even know where to begin looking.
- You have hard-coded the configuration in your application. This can never be changed externally, as opposed to the XML configuration file which let you reconfigure your application from the configuration file rather than modifying your code.
- The workings of your application is now very obscure. You might have tons of beans interacting with each other. You would not be able to see the lifecycle of your beans.
The same, of course, could be said about multiple other annotations, such as @Autowired .
Sample: Bean Instantiation
Another important aspect of the application context configuration XML files is that it let’s you configure how the beans are instantiated. You could look at the configuration and glean valuable insight about the relationship between the beans, as well as which beans were being instantiated and when.
Consider the following:
@Component public class MyComponent { public void doSomething() { //do some component-y stuff here } }
Do you see that @Component ? That is basically declaring that this bean is now a part of an application context (or two) if such an application context exists and if the component scan is properly defined. Compare the code above with the following:
<bean class="com.domain.project.component.MyClass" />
Can you see how this is not hard-coded into the application, and how you can now visibly understand the scope, availability, and configuration of your beans?
Solution
As I said earlier, I am not a particular fan of the XML configuration files. Not for Spring and not for any other framework or tool. For someone not liking it, though, this article seems to be doing a lot of advocating it. That is because we have forgotten that Spring annotation configuration is also a separate configuration for your application which just happens to share the same language as our existing (or new) code base. You have to do your annotation configuration the very same way you were doing your XML configuration files — in a separate, neatly organized place outside of your main code. This has always been the spirit of the Spring framework: Inversion of Control. Manually annotating your properties using annotations is not conceptually any different to manually getting the bean from the context, or manually instantiating it. The only difference is that it is now done with a lot less coding.
It is important to understand that the annotation API shipped with Spring core comes in two different flavors:
- Those that pertain to bean configuration and instantiation via component scan and explicit declaration
- Those that serve to replace the XML configuration with a centralized configuration class file
It is the first category that must be avoided, since the proper usage of these requires much more extra work than is worth it, in my opinion (which would be first writing code devoid of any Spring annotation, then extending all required classes and, finally, annotating them with Spring annotations in a separate module and then incorporating them into the proper application context configurations). They were designed to help with migrating from XML files to the newer paradigm with writing a few lines of code that did the magic.
Now, your application is all shiny and works with annotations and you have actually gotten rid of all those XML configuration files. It’s time to take the next step, and clean up your code.
We will now consider such a change step by step.
The Legacy
Before annotations, we used to write XML configuration files and avoid Spring footprint in our application (in an ideal world, anyway). Suppose that our application has one module, main, and that we have the following XML file under main-module/src/main/resources/config.xml:
<beans> <bean id="first" class="com.domain.project.component.FirstBean" init-method="init"/> <bean id="second" class="com.domain.project.component.SecondBean"> <property name="dependency" ref="first"/> </bean> </beans>
Where the two classes involved are the following:
public class FirstBean { public void init() { //do initialization } } public class SecondBean { private FirstBean dependency; public void setDependency(FirstBean dependency) { this.dependency = dependency; } }
We are told that we need to migrate to annotation configuration. This could be for many different reasons, but in my opinion, the main reason for doing that would be because our XML configuration files have turned into monolithic beasts that are now hard to maintain and test and we need to be able to break them into smaller, more granular configurations that are easier to read and understand.
We now need to take the next step.
The Transition
During the transition, we first heavily leverage the component scan facilities provided by Spring. Our XML file now looks like this:
<beans> <component-scan base-package="com.domain.project" /> </beans>
And we need to modify the two original classes:
@Component public class FirstBean { @PostConstruct public void init() { //do initialization work } } @Component public class SecondBean { @Autowired private FirstBean dependency; }
The first step we took was to transfer all instantiations from the XML configuration file to the actual beans themselves, and then replace setter injection by auto-wiring. This will allow us to empty the XML files while having our applications and beans continue to work the same way as before.
In this step, auto-wiring is a necessity, since we want to migrate from XML to annotation configuration with a minimum number of steps.
For many of us, this is where we stop. And this is where we need to ask ourselves what we have lost. We have lost generality. Our application can now no longer function without Spring. We cannot even see the full composition of our application without the aid of some external tool that lets us view the beans in our context or the dependencies between these beans. Instead of a convenience tool helping us implement the IoC paradigm, it has now become a hard dependency, without which our code will not even compile. That is where we need to step in and clean up the code.
The Cleanup
Spring provides us with the capability to do the same thing without leaving any footprint. We only need to think in parallel with how we did things back during the XML days.
Consider the following configuration class:
@Configuration public class MyConfigurationClass { @Bean public FirstBean firstBean() { final FirstBean bean = new FirstBean(); //instead of @Component bean.init(); //instead of @PostConstruct return bean; } @Bean public SecondBean secondBean(FirstBean firstBean) { final SecondBean bean = new SecondBean(); //instead of @Component bean.setDependency(firstBean); //instead of @Autowired return bean; } }
We now can happily revert our two classes to the state they were before the transition, and everything will continue to work.
This will also present us with the opportunity to collect bean instantiation configurations in smaller configuration classes. If we had one XML file to begin with, it does not necessarily mean that we should have one configuration class for our beans.
Consider the following project packages:
- com.domain.project.component.model
- com.domain.project.component.dataaccess
- com.domain.project.component.dataaccess.dao
- com.domain.project.component.dataaccess.connector
- com.domain.project.component.web
It would make much more sense to create separate configuration classes for each of these packages and configure the beans under those packages separately in each configuration class. Then, we would consolidate all of these under a single, main configuration class that glues the application’s parts together, and bootstraps it for startup.
This main configuration class is really the only place I ever use component scan: to find @Configuration files that would be otherwise very easy to locate in the same application under each package. My rule of thumb is that if I have to use multiple base packages, there is something wrong. It usually means that the configuration classes are not distributed evenly across the application, which means that to find them, I would have to enlist the help of some automated tool. Bad smell. If you — or the next poor developer trying to read and understand your code — cannot find all the configuration classes following a singular, uniform pattern, your code has just become unmaintainable.
One other important byproduct of writing configuration classes on a per-package basis is that you will now be able to identify any possible cyclic dependencies between your packages. Also, you will be able to see if the right level of encapsulation is in place. In the example above, having some bean from the ~.web package reference a bean from ~.dataaccess.connector package is a bad smell and must be investigated.
I also explicitly disable any other annotation being picked up by using the @ComponentScan(useDefaultFilters = false, includeFilters = @Filter(Configuration.class)) component configuration on my main configuration class, which basically says we do not want any of the Spring stereotypes to be picked up except for @Configuration .
Constructor Injection
On a separate note, now that our dependencies are so visible, we can make them even more verbose and make them constructor dependencies. Besides increasing visibility, this will improve lifecycle management: there is no single state in the lifecycle of the bean wherein dependencies are not available. Also, if you are afraid of your constructor requiring too many arguments, then you should really revisit your code. Maybe that is because your class is just doing too many things. Or maybe your dependencies are not being managed well enough.
There can be multiple reasons why you cannot convert a setter injection to a constructor injection:
- You have already released your API and cannot change the constructor or the way dependencies are injected.
- You are writing code in a legacy system which does not allow you to explicitly define such dependencies and therefore you need autowiring to have your system work.
- Your application lifecycle does not allow for the dependency beans to be handed to your dependent beans at construction time.
In the first case, you are pretty much stuck. You cannot change a code that is already released, and that is that. In the second case, you can always write wrappers, proxies, and façades that hide the legacy code and instead allow you to better manage the new code. This is actually what you should really do all the time: isolate legacy code into a manageable corner, and interact with it through a well-maintained interface. In the third case, though, you should really revisit your codebase. Such a case usually indicates a serious case of cyclic dependency in your application, and sooner or later, this will come back to bite you. Maybe not in the form of screwing up your main code, but at least in your tests. This is actually where writing really granular tests and creating configurations for small subsets of your system works to your advantage. You get to spot these eyesores early on.
The Final Step
The final step now is to remove the hard dependency to Spring. Remember how we had a single module with a separate XML configuration file when we started? We had the ability to create a separate module, say, main-spring which had a dependency to our original module, plus Spring. We lost this ability during the transition phase, and tried to rectify it during the cleanup phase. The final step, then, is to introduce the main-spring module and move every Spring related piece of code there. It is important to remember that our application’s components should function without Spring. Also, we should be able to start and run our application by manually doing what Spring would do if we wanted to, even though that might prove cumbersome.
After this final step your application must comply with the following:
- There should not be any XML configuration files left.
- Your main module must have no dependencies to Spring, either direct or transitive.
- Your Spring configuration module must have only dependencies to Spring and your main module.
I would also go so far as say that for each of your components, if you have a separate module, you should have a separate Spring configuration module. Constructor dependencies are not a must, but it is a good practice to fall into.
A Recipe for Migration
If you have not yet made the move to annotation configuration from the original XML configuration files, here is a recipe for doing so. It involves a few easy, deterministic steps that will result in a great experience and very little hitches.
1. Testing, Testing, Testing
Write tests that examine the composition of your components and the presence of dependencies and initializations being performed. Test all the post-processing events that are occurring during your application startup. Writing these tests ensures that the next steps will not break your runtime without breaking your tests first. These tests should be fairly trivial to write and even generate given the original application context configuration files and will easily pass the first time.
Break your context configuration XML files into more granular pieces, possibly one per package so that you can instantiate and run tests more easily.
2. Remove Hard Dependencies
Remove any hard dependencies you might have to Spring contexts. Break your modules into two wherever you have a declared dependency to Spring: one without any dependencies to Spring that contains the logic of your application, and another with a dependency to Spring and your main module, which performs the task of composing and bootstrapping your application. Your XML configuration files should reside in the latter module.
It is important to note that having dependencies to other Spring modules (e.g. Spring Data) is okay, since they are separate projects that do not rely on Spring context being there and can be used and built into your application without ever using Spring context configuration. These should be treated like any other library that your logic depends on, and should therefore stay a part of your original codebase.
3. Change the Bootstrap Context
Change your bootstrap context configuration to Java config. This is the first step towards an annotation-based application. Instead of using ClassPathXmlApplicationContext use the wonderful AnnotationApplicationContext and declare a single entry point into your application by creating a main configuration class. Annotate that class with a component scan for the other Configuration classes as shown above, to only scan for @Configuration -annotated files.
Also manually import all your XML configuration files into your main configuration class. These will be replaced during the course of the transition.
4. Convert Configuration Files
For each XML configuration file, create at least one configuration class. Break the file into multiple classes wherever it makes sense. Try to make your configurations as small as possible. You will be thanking yourself later on when you are trying to run your unit tests and do not have to worry about all the other parts of the application that have to needlessly be configured and started up prior to every test, just because your configuration file included them.
Replace any <beans/> tag with a configuration class, and in it, for each <bean/> tag write a @Bean -annotated factory method that returns the same bean instance with the same configuration.
Remember that when you were doing setter or constructor injection in XML files, you were referring to other beans by the <bean ref=”…”/> attribute. This means that when Spring was creating your bean, it relied on those beans being already instantiated. Follow this same paradigm by declaring such dependencies as factory method parameters.
This means that the following XML configuration code:
<bean id="myBean" class="com.domain.project.component.MyBean"> <parameter name="dependencyOne" ref="firstBean"/> <parameter name="dependencyTwo" ref="secondBean"/> <parameter name="dependencyThree" ref="thirdBean"/> </bean>
will be translated into:
@Bean public MyBean myBean(FirstBean firstBean, SecondBean secondBean, ThirdBean thirdBean) { final MyBean bean = new MyBean(); bean.setDependencyOne(firstBean); bean.setDependencyTwo(secondBean); bean.setDependencyThree(thirdBean); return bean; }
Do not auto-wire bean dependencies into the configuration class. The configuration class will be populated with dependencies when its instantiation is complete, which is when all of its declared beans are instantiated and populated first. What this means is when your factory method is being called to fulfill the instantiation of the configuration class, your dependency bean will be still null and you will end up with weird, hard to trace NullPointerExceptions.
Also, do not call to context methods. If you find yourself calling to methods annotated with @Bean you should start rethinking your approach: the reason you would need such an invocation is because you would want to get a bean from your context before you set up your own bean. This is a classic hard dependency and you are better off turning this into a parameter for the factory method.
5. Get a Beer
After all of this, you need a beer. You deserve it.
Conclusion
In this post I tried to examine the problem of migrating from an XML-driven world wherein we configured our Spring application via XML files to an annotation-driven world. This is a hurt, because we usually get stuck somewhere in between the two paradigms, bringing something from the old world and patching it with the newer concepts, which results in an ugly amalgamation that is hard to maintain and even harder to read.
I then tried to explore a way to do this in a better way, that is verifiable and adheres to the best practices, while not losing any functionality. I hope you have enjoyed this read. If you disagree with any of this or need more clarification, I would be happy to hear about your comments and further discuss them.