Spring break

My witty-title-generator was a bit off for this one. The pun does not imply that Spring is broken, not that it has broken anything. But what I’m going to describe is bound to shake (and break?) a misconception around it.

Consider Spring JavaConfig (now officially part of the Spring distribution). I admit it’s not my favorite configuration option (and – both – regular readers know I have strong opinions on DI). But many people prefer it to XML configuration. And some of them believe something about it, which is not true.

Classes annotated with @Configuration are scanned for bean declarations. At the same time, this annotation also creates a bean for the class. So far, so good. Throw @DependsOn to the picture, which can be used to inform Spring about a dependency which cannot be established through the regular means of constructor or property injection. Don’t get me started about how often people skip proper DI in favor of: getting hold of the context, shamelessly pulling a bean out, then using @DependsOn to force the dependency. I might write another post about this (if I find a way to do it without giving in to my impulse to shout it in all caps and lots of exclamation marks). Suppose, for argument’s sake, that its use is warrantied because of some hidden dependency on a side effect or something.

The problem, which prompted this post, is that some people think that using @DependsOn, from a configuration class to some other configuration class, affects the initialization order of the beans declared in them.

Let me demonstrate what’s wrong with this idea, and show you what @DependsOn really does in that case. Classes foo and bar are configuration classes, but I have also exposed their bean faces, by declaring initialization methods in both of them (imports elided for brevity).

@Configuration
public class foo {
	@PostConstruct
	public void init() {
		System.out.println("foo.init()");
	}

	@Bean
	public Long foobean() {
		System.out.println("foobean");
		return System.currentTimeMillis();
	}
}

@Configuration
@DependsOn("foo")
public class bar {
	@PostConstruct
	public void init() {
		System.out.println("bar.init()");
	}

	@Bean
	public Long barbean() {
		System.out.println("barbean");
		return System.currentTimeMillis();
	}
}

The two unit tests that follow register them in two different orders. Scanning a package is the same, except the registering order is arbitrary and does not lead itself to a repeatable test. I’ll tell you how to conduct that test later, if you want to try it.

	@Test
	public void testDependsOnOrderOne() {
		System.out.println("Running test with bar scanned before foo, and bar having @DependsOn(\"foo\")");
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(bar.class);
		ctx.register(foo.class);
		ctx.refresh();
		final Long foobean = ctx.getBean("foobean", Long.class);
		final Long barbean = ctx.getBean("barbean", Long.class);
		Assert.assertTrue(barbean < foobean);
	}

	@Test
	public void testDependsOnOrderTwo() {
		System.out.println("Running test with foo scanned before bar, and bar having @DependsOn(\"foo\")");
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(foo.class);
		ctx.register(bar.class);
		ctx.refresh();
		final Long foobean = ctx.getBean("foobean", Long.class);
		final Long barbean = ctx.getBean("barbean", Long.class);
		Assert.assertTrue(barbean > foobean);
	}

Have a look at what these tests (which both succeed) output.

Running test with bar scanned before foo, and bar having @DependsOn("foo")
foo.init()
bar.init()
barbean
foobean
Running test with foo scanned before bar, and bar having @DependsOn("foo")
foo.init()
bar.init()
foobean
barbean

Notice that @DependsOn does have an effect. It has the effect it is designed to have: force an initialization order between foo and bar. Notice also that it does not have the effect it is believed to have: force any order between beans inside foo and bar.

Scanning a package is the exact same thing, only the scanning order is not guaranteed. But it’s easy to overcome this problem if first you establish what order happens to be used in your setup, then fixing the @DependsOn the other way and assert that it is ignored.

If you’ve been using @DependsOn between configuration classes, it is time you did some Spring cleaning (a more appropriate pun than the one in the title?).