The Spring Framework is one of my favorite frameworks to use. It just works and it does what you would expect.
Except when it doesn’t.
This week I ran into a little known feature (at least, to me) called “inferred destroy”. This was not a behavior I expected and I had to go digging into the source code and documentation to figure out what was going on.
Spoiler alert, what I’m about to describe isn’t a bug. Rather, it’s something I didn’t understand about the Spring Framework. I’ve been using Spring for over ten years, but as with most things, there always something new to learn. My hope is that you learn from my experience and avoid a few hours of frustration in the future.
The Problem
Let’s start with an example.
Suppose you have two classes: a Spring bean and a configuration object. The Spring bean implements some part of the system. The configuration object creates that bean, using the @Bean
annotation.
The code might look something like this:
1 | public class FooService { |
Looks reasonable enough, right? The FooService
class is created by the FooConfiguration
class, Spring invokes the init()
method, since it has a @PostConstruct
annotation, and the application goes on its merry way. Life is good.
But a curious thing happens when the application shuts down. The final output looks like this:
1 | Initializing FooService |
What the heck is going on? The shutdown()
method is obviously getting called twice! Why would that be?
The Cause
After spending some time in the debugger, I found the class responsible in the Spring Framework. This led me to the documentation, which explained what was happening.
Spring is being a bit too helpful.
When creating beans using @Bean
annotated methods, Spring attempts to automatically detect and register a “destroy” method for you. This means that Spring looks for a no-argument close()
or shutdown()
method somewhere in the class hierarchy, whether it’s declared static or not. If one is found, it will be called when the bean is destroyed.
By adding the @PreDestroy
annotation, I effectively told Spring to call my shutdown()
method twice: once due to the @PreDestroy
annotation and once because the method happens to be called shutdown()
.
This is all in the Spring documentation, but requires a bit of effort to connect the dots. The main Spring documentation states:
Destroy methods are called in the same order:
- Methods annotated with
@PreDestroy
destroy()
as defined by theDisposableBean
callback interface- A custom configured
destroy()
method
If you combine this with the Javadoc for the @Bean
annotation, you get the behavior I described above.
To fix this, I have three options:
Solution 1: Delegate to Inferred Destroy
First, I could remove the @PreDestroy
annotation. This way, I rely on the “destroy method inference” to take over.
This approach isn’t as appealing to me for a few reasons:
- It is less clear. Obviously I didn’t know that the
shutdown()
method would automatically get called by Spring. - It is less maintainable. Not only is it less clear, itself a maintainability problem, but now I have to keep an eye on Spring’s release notes to determine if this behavior changes over time.
- It is fragile. There is not a corresponding “inferred init” pattern. So the class has a
@PostConstruct
, but there isn’t a corresponding@PreDestroy
. Chances are good that another developer might look at the code and think the@PostConstruct
annotation was accidentally left off.
Solution 2: Disable the Default Inference
Second, I could explicitly set the destroyMethod on the @Bean
annotation to blank, like this:
1 |
|
The default value for the destroyMethod is “(inferred)”. By setting it to blank, we explicitly tell Spring not to register a destroy method with the bean.
This is better. We keep the balanced @PostConstruct
and @PreDestroy
in the class, but we have to remember to override the destroyMethod
attribute in the @Bean
annotation.
Solution 3: Use Component Scanning
The final way to solve the issue is to simply let the class get picked up by your application’s component scanning. To do this, add a @Service
annotation to the FooClass
and make sure the class is in the list of packages scanned by Spring. Beans found during component scanning do not have the same “inferred destroy method” behavior as the @Bean
annotation does.
This solves the issues with both of the previous solutions. It keeps the @PostConstruct
and @PreDestroy
annotations and there is no @Bean
annotation to update.
Of course, this won’t work if you don’t have control over the class in question. In that situation it probably doesn’t have the Spring annotations either. But if it does, the second solution is your only option.
I suspect you’d rather spend your time solving business problems. Hopefully this prevents you from wasting a bunch of time, so you can spend your time much more productively. Instead you can track down a different obscure bug introduced by a different library you’re using.
Question: Have you run into this problem? What did you do to solve it?