12. January 2021
by Moritz Kammerer | 1224 words | ~6 min read
In this blog post, we’ll look at various ways to decorate a Spring bean. The decorator pattern is a software engineering pattern “that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.” 1
First, let’s create our interface:
and one simple implementation:
Now, let’s create our decorator object which will decorate other
As you see, this class takes another instance of
OurService via constructor injection and stores it in the
doSomething() method is called (that’s the method defined in the interface), first a log message is printed and then
the delegate is called.
Using this in a non-Spring application is really easy:
We just created a new
LoggingOurService and passed a new
OurServiceImpl via the constructor.
This setup prints:
de.qaware.blog.decorator.decorator.impl.LoggingOurService - doSomething() called de.qaware.blog.decorator.decorator.NonSpringMain - Result: 'something'
the “doSomething() called” log message is from
LoggingOurService, the result is returned from the delegate
So far, so good. Now let’s get this setup running in Spring. I’m using Spring Boot here, but all this stuff is also applicable to plain Spring.
Our first attempt looks like this:
This defines two beans, first the delegate and second the decorator. The decorator has a dependency on the delegate.
Now we have to use that bean somewhere. We’ll use a
CommandLineRunner which gets executed when the application is started:
When we start that application, Spring fails with:
Parameter 0 of constructor in de.qaware.blog.decorator.decorator.Runner required a single bean, but 2 were found: - ourServiceDelegate: defined by method 'ourServiceDelegate' in class path resource [de/qaware/blog/decorator/decorator/OurConfiguration.class] - loggingOurService: defined by method 'loggingOurService' in class path resource [de/qaware/blog/decorator/decorator/OurConfiguration.class]
Spring is complaining that it found 2 beans and now it’s confused which one to inject into the
One way to fix that is to mark the
loggingOurService bean method as
Now Spring knows that if there are multiple beans of that type (
OurService), it has to pick the one marked as
Another option is to use
but now you have to change all the injection points to use the qualifier, too:
Spring still finds two beans, but as the injection point in the
Runner class has the same qualifier as the
@Bean method, Spring knows which
bean to pick.
That’s the story so far if you have control over the
@Bean methods, as you’ll either have to add
@Qualifier to them.
But what should we do if we don’t have the ability to change those methods, for example if you try to decorate beans which are created by an autoconfiguration?
There must be a way in the mighty Spring framework?
And of course, there is. The concept is called
which is an interface we’ll have to implement. The processor is called for each Spring bean in the context and has the ability to replace the bean
with some other bean.
First, let’s take a look at our configuration:
This configuration class just creates the delegate service and a bean processor. It does not create the
The bean processor is where the magic happens:
postProcessAfterInitialization gets called for every bean in the Spring context.
if statement returns early if the bean is not of type
if statement returns early if the bean is already of type
LoggingOurService, as we don’t want to wrap a
LoggingOurService in another
The last line wraps a new
LoggingOurService around the bean (which is a
OurService) and returns it. Spring now replaces the original bean (the one in the
bean argument, type
the one returned from this method (type
Let’s run our application:
guration6$LoggingOurServiceBeanProcessor : Decorating bean of type de.qaware.blog.decorator.decorator.impl.OurServiceImpl d.q.blog.decorator.decorator.Runner : Class of ourService: de.qaware.blog.decorator.decorator.impl.LoggingOurService d.q.b.d.d.impl.LoggingOurService : doSomething() called d.q.blog.decorator.decorator.Runner : Result: 'something'
As you see from the first log message, our bean processor has been called and decorated a bean of type
When the bean is used in the
Runner, it’s no longer of type
OurServiceImpl, but of type
LoggingOurService because that’s the
bean the post processor has created.
And that’s how you decorate beans for which you can’t change the
The banner image is from Wikipedia.
Gamma, Erich; et al. (1995). Design Patterns. Reading, MA: Addison-Wesley Publishing Co, Inc. pp. 175ff. ISBN 0-201-63361-2. ↩︎