13 February 2013

Breaking the Spring by example

First of all, I'm not against Spring, IoC frameworks or frameworks in general. Quite the contrary, I'm big fan of many different frameworks. Said that I must admit that sometimes when a framework hides too much magic you risk loosing control of your application. Once you realize that you've just created zombie app you are afraid of, it may be too late ;) So be careful with framework selection even if you think of commonly used framework like Spring...

Spring - ultimate IoC container


Spring Framework is one of the most popular IoC frameworks and probably the richest in terms of provided features. With every new release number of capabilities, supported technologies increases making the Spring attractive candidate for wide range of Java projects.

One would expect that using such matured, popular, battle-proofed framework should be easy and straightforward at least when it comes to its core functionality. You just annotate your beans, create configuration bean and everything should work as expected. But sometimes it doesn't... As it turns out, having large number of capabilities doesn't mean you can freely play with them, mix them and expect that Spring handles all cases seamlessly.

While working recently with Spring I've found several cases when mixing some features resulted in unexpected or undocumented behavior. I was also surprised to find out
that some features were missing and to solve the problem I had to find a workaround.

Don't have to say, the often you are being surprised by the framework, the often you are wondering if the decision of using it was correct... After all, the time you lost on learning nuances, limitations and bugs of the framework you could better spend on implementing your own infrastructure, especially if all that you need is just a small subset of what the framework provides.

Let's go to the examples.
I will show you how to make Spring surprise you (well, at least it surprised me).

Let's break the Spring

1) Lazy secondary beans (using @Primary and @Lazy annotations)

Spring provides @Primary annotation that should be used to indicate "that a bean should be given preference when multiple candidates are qualified to autowire". Great, that could be useful for tests. By configuring primary beans inside test configuration we could easily replace default beans from main configuration.

Main configuration:

Additional test configuration:


When starting test, the test service will be instantiated. Since default service bean is configured as lazily instantiated, it should not be instantiated by Spring because it is not used at all, right? Well, unfortunately Spring ignores @Lazy annotation in this case. So you can be surprised if you put some heavy initialization logic inside your default service and expect that it will not be executed during the tests...

2) @Primary annotation and service locator

Sometimes when working with prototype scoped beans it is necessary to ask Spring for specific bean using service locator pattern. Please see example below that complements the code from example 1:

Surprisingly Spring will throw exception when trying to run the code above complaining that two candidates of type MyService are available... This is just a bug, nothing more.

3) Events and prototype listeners

Spring provides support for event base communication. You can easily publish application event from one corner of your system and handle the event on the other corner by registering appropriate event listener. It works until you need your listener to be prototype scoped bean.
The Spring will notify only one instance of listener about the event, the instance that has been created during Spring initialization (when ContextRefreshedEvent is published by Spring). There is no way to inform all instantiated prototypes about the event.

4) Custom qualifiers and annotation-based configuration

Using annotations like @Qualifier you can control the selection of candidates among multiple matches. But default usage of @Qualifier allows only bean names (Strings) as identifiers that is error-prone and refactoring-unfriendly.

Fortunately there is another solution. You can define your own annotation that can be used as classifier. Just use @Qualifier as meta-annotation! Example of such custom qualifier you can find below:

You can than define several beans of the same type but with different qualifier type.

..and inject independently beans with different qualifiers:

So far so good. Now, where is the surprise you may ask... As you probably noticed, we used xml configuration to define beans with different qualifiers. Nobody uses xml nowadays ;) so there must be a good reason to use it... The reason is that (surprise!) you can't define several beans of the same type with different custom qualifier using annotation-based configuration because it is not possible to annotate factory method with custom qualifier:

! The code below does not compile !


You can only annotate the type with custom qualifier:

but this approach requires new class for each qualifier type.

I don't know why Spring does not support annotating factory method inside configuration as described above. You will find only the following explanation In Spring documentation:

"As with most annotation-based alternatives, keep in mind that the annotation metadata is bound to the class definition itself, while the use of XML allows for multiple beans of the same type to provide variations in their qualifier metadata, because that metadata is provided per-instance rather than per-class."

5) Properties and annotation parameters

What's strange about the following code?

Configuration for scheduler 1 is read from  Properties while for scheduler 2 it is hardcoded. Why? Because you can assign property expression (which is String) only to parameters of type String. Unfortunately fixedRate parameter is of type long... The solution would be to switch to xml configuration (again? oh no..) or implement some workaround: http://stackoverflow.com/questions/11608531/injecting-externalized-value-into-spring-annotation.

6) ...

I guess if you worked with Spring you could put another case here.
Feel free to drop a comment on your biggest surprise from Spring.


3 comments:

  1. Great article and thorough observations! However if you believe something is a bug or needs an improvement, just report it. And few technical comments of mine:

    Ad. 1.: That looks like a bug. On the other hand you shouldn't put any heavyweight initialization into constructor. Are lifecycle callbacks called as well (I guess they are...)?

    Ad. 2.: @Primary is documented to only reflect autowiring. getBean() simply ignores it. You still have two instances in your context and this is what Spring says.

    Ad. 3.: Spring does not keep reference to prototype scoped beans. That's why destroy callback is not called for them (this is documented).

    Ad. 5.: indeed, that's unfortunate. As a workaround you can use cron expression to express simple interval - but that seems like an overkill.

    Ad. 6.: ...how @Transactional plays with different types of proxies and this: http://blog.ethlo.com/2013/01/30/spring-defaultkeygenerator-gotcha.html

    ReplyDelete
  2. Thanks.

    >> Ad. 2.: @Primary is documented to only reflect autowiring. getBean() simply ignores it.

    Ok, so it's not a bug, it's a feature :)

    >> Ad. 3.: Spring does not keep reference to prototype scoped beans.

    Ok, so exception should be thrown during Spring initialization if prototype scoped event listener has been detected.

    >> Ad. 5.: [...] you can use cron expression to express simple interval [...]

    And I did it. Fortunatelly I didn't have to create cron expression for 92 min interval: http://www.vim-fu.com/90-minute-cron-job/


    Sure, detected bugs should be reported. But more often than not its not a bug, its a feature :)

    ReplyDelete
  3. I agree with Tomasz that bugs as well as improvements should be reported. This is the idea of open source ;)

    I cannot agree with a statement that frameworks shouldn't hide "too much magic". Frameworks are high level abstraction over some low level concepts, or just provide features which make no sense to implement over and over the same way. It is the role of a frameowrk to hide magic. The problem is when the framework is trying to do too much things. In such case framework should be split into independent modules (and Spring is somewhat modularized).

    Second thing is picking the right tool for the right problem. If you need only a subset of functionalities that Spring provides, why use Spring in first place? Why not go with something lightweight like Guice?

    Finally annotation based and xml-less configuration is relatively new in Spring (comparing to other features), so it could have bugs. It isn't also used as much, as developers seems to be using Spring in traditional way. I'm sure that Spring team will appreciate any bug reports and suggestions how to improve their framework.

    ReplyDelete