JVM Advent

The JVM Programming Advent Calendar

Spring’s @Primary annotation in action

Spring is a framework that never stops to amaze me. It’s because of the fact that it offers plenty of different solutions that allow us, developers, to complete our tasks without writing millions of lines of code. Instead we are able to do the same in a much more readable, standardized manner. In this post I will try to describe one of its features that most likely is well known to all of you but in my opinion its importance is undervalued. The feature that I’ll be talking about is the @Primary annotation.

The problem

On a couple of projects that I was working on we have came accross a common business problem – we had a point of entry to a more complex logic – some container, that would gather the results of several other processors into a single output (something like map-filter-reduce functions from the functional programming). To some extent it resembled the Composite pattern. Putting it all together our approach was as follows:

  1. We had a container that had an autowired list of processors implementing a common interface
  2. Our container implemented the same interface as the elements of the autowired list
  3. We wanted the client class that would use the container to have this whole processing work transparent – he is interesed only in the result
  4. The processors have some logic (predicate) basing on which a processor is applicable to the current set of input data
  5. The results of the processing were then combined into a list and then reduced to a single output
There are numerous ways of dealing with this issue – I’ll present one that uses Spring with the @Primary annotation.

The solution

Let’s start with defining how our use case will fit to the aforementioned preconditions. Our set of data is a Person class that looks as follows:

Person.java

package com.blogspot.toomuchcoding.person.domain;

public final class Person {
private final String name;
private final int age;
private final boolean stupid;

public Person(String name, int age, boolean stupid) {
this.name = name;
this.age = age;
this.stupid = stupid;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public boolean isStupid() {
return stupid;
}
}

Nothing out of the ordinary. Now let us define the contract:

PersonProcessingService.java

package com.blogspot.toomuchcoding.person.service;

import com.blogspot.toomuchcoding.person.domain.Person;

public interface PersonProcessingService {
boolean isApplicableFor(Person person);
String process(Person person);
}

As stated in the preconditions each implementaiton of the PersonProcessingService has to define two points of the contract :

  1. whether it is applicable for the current Person 
  2. how it processess a Person.

Now let’s take a look at some of the Processors that we have – I’ll not post the code here cause it’s pointless – you can check out the code later on Github or on Bitbucket. We have the following @Component annotated implementations of PersonProcessingService:

  • AgePersonProcessingService
    • is applicable if Person’s age is greater or equal 18
    • returns a String containing “AGE” as processing takes place – that’s kind of silly but it’s just a demo right? 🙂
  • IntelligencePersonProcessingService
    • is applicable if a Person is stupid
    • returns a String containing “STUPID” as processing takes place
  • NamePersonProcessingService
    • is applicable if a Person has a name
    • returns a String containing “NAME” as processing takes place
The logic is fairly simple. Now our container of PersonProcessingServices would want to iterate for a given Person over the processors, check if the current processor is applicable (filter) and if that is the case add the String that is a result of processing of a Person to the list of responses (map – a function converting a Person to a String) and finaly join those responses by a comma (reduce). Let’s check it out how it’s done:

PersonProcessingServiceContainer.java

package com.blogspot.toomuchcoding.person.service;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import com.blogspot.toomuchcoding.person.domain.Person;

@Component
@Primary
class PersonProcessingServiceContainer implements PersonProcessingService {

private static final Logger LOGGER = LoggerFactory.getLogger(PersonProcessingServiceContainer.class);

@Autowired
private List<PersonProcessingService> personProcessingServices = new ArrayList<PersonProcessingService>();

@Override
public boolean isApplicableFor(Person person) {
return person != null;
}

@Override
public String process(Person person) {
List<String> output = new ArrayList<String>();
for(PersonProcessingService personProcessingService : personProcessingServices){
if(personProcessingService.isApplicableFor(person)){
output.add(personProcessingService.process(person));
}
}
String result = StringUtils.join(output, ",");
LOGGER.info(result);
return result;
}

public List<PersonProcessingService> getPersonProcessingServices() {
return personProcessingServices;
}
}


As you can see we have a container that is annotated with @Primary which means that if an implementation of the PersonProcessingService will have to be injected then Spring will pick the PersonProcessingServiceContainer to be injected. The cool thing is that we have an autowired list of PersonProcessingServices which means that all other implementations of that interface will get autowired there (the container will not autowire itself to the list!).

Now let’s check out the Spock tests that prove that I’m not telling any lies. If you aren’t using Spock already in your project then you should move it straight away 🙂

PersonProcessingServiceContainerIntegrationSpec.groovy

package com.blogspot.toomuchcoding.person.service
import com.blogspot.toomuchcoding.configuration.SpringConfiguration
import com.blogspot.toomuchcoding.person.domain.Person
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import spock.lang.Unroll

import static org.hamcrest.CoreMatchers.notNullValue

@ContextConfiguration(classes = [SpringConfiguration])
class PersonProcessingServiceContainerIntegrationSpec extends Specification {

@Autowired
PersonProcessingService personProcessingService

def "should autowire container even though there are many implementations of service"(){
expect:
personProcessingService instanceof PersonProcessingServiceContainer
}

def "the autowired container should not have itself in the list of autowired services"(){
expect:
personProcessingService instanceof PersonProcessingServiceContainer
and:
!(personProcessingService as PersonProcessingServiceContainer).personProcessingServices.findResult {
it instanceof PersonProcessingServiceContainer
}
}

def "should not be applicable for processing if a person doesn't exist"(){
given:
Person person = null
expect:
!personProcessingService.isApplicableFor(person)
}

def "should return an empty result for a person not applicable for anything"(){
given:
Person person = new Person("", 17, false)
when:
def result = personProcessingService.process(person)
then:
result notNullValue()
result.isEmpty()
}

@Unroll("For name [#name], age [#age] and being stupid [#stupid] the result should contain keywords #keywords")
def "should perform different processing depending on input"(){
given:
Person person = new Person(name, age, stupid)
when:
def result = personProcessingService.process(person)
then:
keywords.every {
result.contains(it)
}
where:
name | age | stupid || keywords
"jan" | 20 | true || ['NAME', 'AGE', 'STUPID']
"" | 20 | true || ['AGE', 'STUPID']
"" | 20 | false || ['AGE']
null | 17 | true || ['STUPID']
"jan" | 17 | true || ['NAME']
}
}

The tests are pretty straight forward:

  1. We prove that the autowired field is in fact our container – the PersonProcessingServiceContainer.
  2. Then we show that we can’t find an object in the collection of autowired implementations of the PersonProcessingService, that is of PersonProcessingServiceContainer type
  3. In the next two tests we prove that the logic behind our processors is working
  4. Last but not least is the Spock’s finest – the where clause that allows us create beautiful paramterized tests.

Per module feature

Imagine the situation in which you have an implementation of the interface that is defined in your core module.

@Component
class CoreModuleClass implements SomeInterface {
...
}

What if you decide in your other module that has the dependence to the core module that you don’t want to use this CoreModuleClass and want to have some custom logic wherever the SomeInterface is autowired? Well – use @Primary!

@Component
@Primary
class CountryModuleClass implements SomeInterface {
...
}

In that way you are sure that wherever the SomeInterface has to be autowired it will be your CountryModuleClass that will be injected in the field.

Conclusion

In this post you could see how to

  • use the @Primary annotation to create a composite like container of interface implementations
  • use the @Primary annotation to provide a per module implementation of the interface that will take precedence over other @Components in terms of autowiring
  • write cool Spock tests 🙂

The code

You can find the code presented here on Too Much Coding’s Github repository or on Too Much Coding’s Bitbucket repository.

Author: Marcin Grzejszczak

Author of “Mockito Instant” and “Mockito Cookbook” books. OSS Contributor. Co-founder of the Warsaw Groovy User Group and Warsaw Cloud Native Meetup.

Lead of Spring Cloud Sleuth, Spring Cloud Contract and Spring Cloud Pipelines projects at Pivotal

Contributed to Groovy, Mockito, Rest-assured, Drools, Moco. Author of Uptodate Gradle plugin, Spock subjects-collaborators extension, gradle-test-profiler, JSONAssert and XMLAssert open source projects.

Next Post

Previous Post

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2024 JVM Advent | Powered by steinhauer.software Logosteinhauer.software

Theme by Anders Norén