12. January 2021
by Moritz Kammerer | 1242 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 OurService
instances:
|
|
As you see, this class takes another instance of OurService
via constructor injection and stores it in the delegate
field.
When 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 OurServiceImpl
.
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 Runner
.
One way to fix that is to mark the loggingOurService
bean method as @Primary
:
|
|
Now Spring knows that if there are multiple beans of that type (OurService
), it has to pick the one marked as @Primary
.
Another option is to use @Qualifier
:
|
|
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 @Primary
or @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 BeanPostProcessor
,
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 LoggingOurService
.
The bean processor is where the magic happens:
|
|
The postProcessAfterInitialization
gets called for every bean in the Spring context.
The first if
statement returns early if the bean is not of type OurService
.
The second if
statement returns early if the bean is already of type LoggingOurService
, as we don’t want to wrap a LoggingOurService
in another LoggingOurService
.
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 OurService
) with
the one returned from this method (type LoggingOurService
).
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 OurServiceImpl
.
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 @Bean
methods.
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. ↩︎