Friday, May 23, 2014

So Long Spring XMLs... (@Configuration class quick start guide)

This post is based on a tech-talk I gave in Outbrain

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:

  1. No typos - You can't have a typo in code. The code just won't compile
  2. 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
  3. 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.
  4. 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).
We notice that we're missing the property someInterestingProperty and the bean beanFromSomewhereElse.

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:

19 comments:

  1. What a fantastic overview of very key ideas, especially for a Spring newbie like me! Thank you.

    ReplyDelete
  2. Great! I'm very glad you enjoyed it and found it beneficial

    ReplyDelete
  3. Great post, nicely explained... but I am sure you really didn't meant "pubic" in your step 4 second code block!!

    ReplyDelete
  4. Great post, nicely explained... but I am sure you really didn't meant "pubic" in your step 4 second code block!!

    ReplyDelete
    Replies
    1. Thanks Zeeshan! I'm very glad you found this post useful. I'm not sure about your comment. Why didn't I mean for this to be public? That's what I did with other beans as well, and because it's configuration and not core-code, I'm not sure that matters. What do you think?

      Delete
  5. Liked it a lot.! For a newbie to spring like me, it gives such a detailed and clear explanation. I have one doubt though. Here for 'anotherBean' you are doing a constructor injection. What if I have to do a setter injection..? How can that be achieved.?

    ReplyDelete
    Replies
    1. That's a good question. The answer is quite simple - all you have to do is to instantiate your object (by calling its constructor) and then invoke the relevant setter with the bean you would like to inject. That's as simple as that.

      Delete
    2. Can you please write a code snippet for it ?

      Delete
  6. Replies
    1. Thank you very much Luca. I'm happy to hear you enjoy my blog.

      Delete
  7. Thanks for sharing. Great job!
    Keep up the good work!

    ReplyDelete
  8. Very good explanation of key concepts.
    Thank you so much.

    ReplyDelete
  9. Thanks for the article, its really helpful.
    I have a query. In Step4 you mentioned that 'Import beans from other XMLs OR @Configuration class, OR @Component etc... classes)'
    And the next states that '
    In order to use beans that WERE NOT defined in this @Configuration class .... ' can you please clarify that ?

    ReplyDelete