This time I decided to dedicate the post to something a bit more technical than my usual posts. In this post I will try to show those of you that use XMLs to define their Java Spring application context, how to use a method which I find much more convenient for most cases - the spring @Configuration class.
When Spring just started, the only way to configure the wirings of an application, was to use XMLs which defined the dependencies between different beans. As Spring had continued to develop, 2 more methods were added to configure dependencies - the annotation method and the @Configuration method.
What is this @Configuration class?
You can think of a @Configuration class just like XML definitions, only defined by code. Using code instead of XMLs allows some advantages over XMLs which made me switch to this method:- No typos - You can't have a typo in code. The code just won't compile
- Compile time check (fail fast) - With XMLs it's possible to add an argument to a bean's constructor but to forget to inject this argument when defining the bean in the XML. Again, this can't happen with code. The code just won't compile
- IDE features come for free - Using code allows you to find usages of the bean's constructor to find out easily the contexts that use it; It allows you to jump back and forth between beans definitions and basically everything you can do with code, you get for free.
- Feature flags - In Outbrain we use feature-flags a lot. Due to the continuous-deployment culture of the company, a code that is pushed to the trunk can find itself in production in a matter of minutes. Sometimes, when developing features, we use feature flags to enable/disable certain features. This is pretty easy to do by defining 2 different implementations to the same interface and decide which one to load according to the flag. When using XMLs we had to use the alias feature which makes it not intuitive enough to create feature-flags. With @Configuration, we can create a simple if clause for choosing the right implementation.
Our example case
So, let's start with a simple example of a Spring XML, and migrate it to Spring @Configuration class:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <import resource="another-application-context.xml"/> <bean id="someBean" class="avi.etzioni.spring.configuration.SomeClassImpl"> <constructor-arg value="${some.interesting.property}" /> </bean> <bean id="anotherBean" class="avi.etzioni.spring.configuration.AnotherClassImpl"> <constructor-arg ref="someBean"/> <constructor-arg ref="beanFromSomewhereElse"/> </bean> </beans>
Step 1: Migrate <beans> to @Configuration
In XMLs the highest tag in the hierarchy is <beans>. This tag will be replaced with a class, annotated with @Configuration
@Configuration public class ByeXmlApplicationContext { }
Step 2: Create a method for each Bean
Each <bean> tag in the XML will be replaced with a method that's annotated with @Bean annotation. Usually it would be a better practice for the method to return an interface type as follows:
@Configuration public class ByeXmlApplicationContext { @Bean(name = "someBean") public SomeClass getSomeClass() { return new SomeClassImpl(someInterestingProperty); } @Bean(name = "anotherBean") public AnotherClass getAnotherClass() { return new AnotherClassImpl(getSomeClass(), beanFromSomewhereElse); } }
A few things to notice:
- Each method is defined to return an interface type. In the method body we create the concrete class.
- The name that's defined in the @Bean annotation is the same as the id that is defined in the XML for the beans.
- The bean anotherBean is injected with someBean in the XML. In the scenario here, we just call the getSomeClass() method. This doesn't create another bean, this just uses the bean someBean (the same as it was in the XML).
Step 3: Import other XMLs or other @Configuration classes
The bean beanFromSomewhereElse comes from a different XML file named another-application-context.xml and which was imported in the original XML. In order to use it, we need to import this XML here as well. To do so, we'll just annotate the class with the annotation @ImportResource as follows:
@ImportResource("another-application-context.xml") @Configuration public class ByeXmlApplicationContext {. . .
}
That's in fact equivalent to the <import resource=""/> tag in the XML format.
If this bean resides in another @Configuration class you can use a different annotation @Import to import it:
@Import(OtherConfiguration.class) @Configuration public class ByeXmlApplicationContext { ... }
In order to complete the picture, here's how you can import a @Configuration class from an XML configuration file:
<context:annotation-config/>
<bean class="some.package.ByeXmlApplicationContext"/>
The <context:annotation-config/> needs to be defined once in the context in order to make spring aware to @Configuration classes
Step 4: Import beans from other XMLs (or @Configuration class, or @Component etc... classes)
In order to use beans that were not defined in this @Configuration class we can either declare a private member annotated with @Autowired and @Qualifier as follows:
@Autowired @Qualifier(value = "beanFromSomewhereElse") private final StrangeBean beanFromSomewhereElse;
This member can now be used to construct the bean anotherBean.
Another option is to declare a method argument to getAnotherClass() as follows:
@Bean(name = "anotherBean") public AnotherClass getAnotherClass(@Qualifier (value = "beanFromSomewhereElse")
final StrangeBean beanFromSomewhereElse) { return new AnotherClassImpl(getSomeClass(), beanFromSomewhereElse); }
I usually prefer the first method as it is less verbose. But of course, that's just a matter of taste.
Just remember - the beans you import must be loaded to the application context - either by @Import or @ImportResource from this class, or using any other method from anywhere else (XML, @Configuration or annotations).
Step 5: Import properties
So, we still need to import somehow the property someInterestingProperty which was defined in the XML using ${some.interesting.property}. Well, that will be very similar to autowiring a bean, but instead of the @Qualifier annotation, we'll use the @Value annotation:
@Autowired @Value("${some.interesting.property}") private final String someInterestingProperty;
You can also use a SpEL (Spring Expression Language) expressions with @Value.
Step 6: Import @Configuration from web.xml
At the final step, we would like to be able to import an entire application-context without using any XMLs. If we have a web app, this can be done by declaring the class in the web.xml as follows:
<servlet> <servlet-name>my-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value>some.package.ByeXmlApplicationContext</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
Summary
As you can see - spring @Configuration classes can be a powerful tool in defining your application context. But with great power comes great responsibility. Code is much easier to abuse than XMLs. It's easy to make complexed @Configuration classes. Try to think of the @Configuration class as a more flexible XMLs and behave them as if they were XMLs:
- Split to different @Configuration classes and don't put all of your beans in one class
- Give meaningful names and even decide on a naming convention
- Avoid any logic inside the @Configuration classes. Aside maybe for things like feature-flags.
Of course, I just gave here the basics. The internet is full of resources about using @Configuration classes. And of course, you are more than welcome to contact me for any further help. I'll do my best to assist.
Find me on Twitter: @AviEtzioni
More interesting posts from this blog: