DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Dependency Injection in Spring
  • Mastering Spring: Synchronizing @Transactional and @Async Annotations With Various Propagation Strategies
  • Composing Custom Annotations in Spring
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements

Trending

  • Securing the Future: Best Practices for Privacy and Data Governance in LLMOps
  • Cloud Security and Privacy: Best Practices to Mitigate the Risks
  • Rust, WASM, and Edge: Next-Level Performance
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  1. DZone
  2. Coding
  3. Languages
  4. Smart Dependency Injection With Spring: Assignability (Part 2 of 3)

Smart Dependency Injection With Spring: Assignability (Part 2 of 3)

In part two of this three-part series, learn about using inheritance to simplify injecting beans with Spring Framework.

By 
Arnošt Havelka user avatar
Arnošt Havelka
DZone Core CORE ·
Updated Nov. 09, 21 · Tutorial
Likes (8)
Comment
Save
Tweet
Share
14.5K Views

Join the DZone community and get the full member experience.

Join For Free

Preface

The Spring Framework  is a very powerful framework and provides first class support for dependency injection (DI). This article is the second one in my series dedicated to dependency injection  with Spring Framework. My series is split into these three articles:

  • Basic usage of DI
  • DI with assignability (this article)
  • DI with generics

In This Article, You Will Learn:

  • How to inject beans by a common interface
  • How to inject beans by ancestor (usually abstract) class
  • How to inject beans by annotation

Overview

In my previous article, I reviewed the DI basics: different configuration types, injection variants, injection rules, or injected types. Most of it is well known and that article serves just as a summary.

Now, we move the DI usage to the next level with an assignability. We talk about injecting beans with:

  1. Inheritance: inject beans based on their ancestor
  2. Implementing an interface: inject beans implementing this interface.

Let's start with an explanation of the used classes in this article.

Domain

Before we start the explanation, we need to know something about the used domain classes. In this article, we use a beverage domain with several classes and interfaces grouped into two parts:

  • Carbonated beverages: three classes inheriting from the AbstractCarbonatedBeverage class.
  • Hot beverages: three classes implementing the HotBeverage interface. 

The relationship between these classes and their relationship is depicted below.

The class hierarchy of the beverage domain

The class hierarchy of the beverage domain

Beverage Interface

The root element in our domain is a Beverage interface with a single method called getName. The goal of this method is to return the real name of the beverage.

Java
 
public interface Beverage {
	
	String getName();

}

AbstractCarbonatedBeverage Class

The first beverage group inherits from AbstractCarbonatedBeverage abstract class annotated by our custom @Carbonated annotation.

Java
 
@Carbonated
public abstract class AbstractCarbonatedBeverage implements Beverage {
	
}

The @Carbonated annotation serves as a class marker and has this definition:

Java
 
@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Carbonated {

}

The example of a class implementing the AbstractCarbonatedBeverage class is the Cola class defined as:

Java
 
@Component
public class Cola extends AbstractCarbonatedBeverage {

	@Override
	public String getName() {
		return "Cola";
	}

}

Note: The rest of the classes (Beer and Soda) are defined in the same way.

HotBeverage Interface

The other part of the beverage domain is grouped around the HotBeverage interface. This interface serves as a marker (just in a different way) and is defined as:

Java
 
public interface HotBeverage extends Beverage {
	
}

The example of the class inheriting from the HotBeverage abstract class is the Tea class defined as:

Java
 
@Component
@Primary
public class Tea implements HotBeverage {

	@Override
	public String getName() {
		return "Tea";
	}

}

Note: The Tea class is the only class having the @Primary annotation. This means the tea bean is the default one in the whole beverage group.

You can find all domain classes in my GIT repository.

Injecting Single Bean

As promised in the previous article, we cover all examples of the rules of injection here. All examples below inject beans by the assignable type and not the real type itself (as it is the purpose of this article).

Injection by Name

In this case, we have several beans inheriting from the same class (e.g. AbstractCarbonatedBeverage class in our case).  Therefore the simplest solution is to name the injected property according to the bean qualifier value. We can demonstrate it by injecting the cola beverage via AbstractCarbonatedBeverageclass:

Java
 
@Autowired
private AbstractCarbonatedBeverage cola;

We can easily verify the correctness of the injection in the BeverageSingleWiringTest class. There is the shouldWireBeanByName method that checks the injected instance and verifies it's really an instance of the Cola class.

Java
 
@SpringBootTest(classes = WiringConfig.class)
class BeverageSingleWiringTest {
	
	@Autowired
	private AbstractCarbonatedBeverage cola;

	@Test
	void shouldWireColaByName() {
		assertThat(cola.getName()).isEqualTo("Cola");
	}

}

Similarly, we can inject beer beverage :

Java
 
@Autowired
private AbstractCarbonatedBeverage beer;

@Test
void shouldWireBeerByName() {
	assertThat(beer.getName()).isEqualTo("Beer");
}

and soda beverage.

Java
 
@Autowired
private AbstractCarbonatedBeverage soda;

@Test
void shouldWireSodaByName() {
	assertThat(soda.getName()).isEqualTo("Soda");
}

Note: We are not able to inject any bean implementing the HotBeverage interface like this, because we have a primary bean defined there (see the next chapter).

Injection of Primary Bean

In some cases, we want to have a default bean for the type (the Beverage interface here). In our domain, the Tea class is defined as the primary bean. So we can declare the property type as the Beverage class and name the property as soda (even though Spring has soda bean available) and we still get an instance of the Tea class.

Java
 
@Autowired
private Beverage soda;

The obvious test to verify DI behavior represents the shouldWirePrimaryBean test method looking like this:

Java
 
@Test
void shouldWirePrimaryBean() {
	assertThat(soda.getName()).isEqualTo("Tea");
}

In order to get the desired bean and avoid @Primary annotation issue, we need to use a qualifier. Let's see this in the following example.

Note: I consider the primary bean to be a little bit tricky. Therefore, I prefer to avoid this approach whenever possible. 

Injection by Qualifier

The highest precedence in the injection rule hierarchy is the injection by a qualifier. We have the same case as before (the injection by the Beverage interface), but this time we also have @Qualifier annotation with the correct bean name.

Java
 
@Autowired
@Qualifier("soda")
private Beverage qualifiedBeverage;

Our shouldWireBeanByQualifier test method verifies, the injected bean is really the soda bean.

Java
 
@Test
void shouldWireBeanByQualifier() {
	assertThat(qualifiedBeverage.getName()).isEqualTo("Soda");
}

Note: It doesn't matter how we call a property name in our class where we want to inject the bean. The qualifier has the precedence before primary bean or the name rule.

Injection by Annotation

The last option demonstrated here (to narrow the list of available beans to a single instance) is to use some annotation. We have the @Alcoholic annotation class for this purpose defined like this :

Java
 
@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Alcoholic {

}

The usage is very simple. We need to specify the @Alcoholic  annotation (at least in the injection point). Here we want to inject the beer bean as:

Java
 
@Autowired
@Alcoholic
private Beverage coldBeer;

The verification is similar to all previous test cases above.

Java
 
@Test
void shouldWireBeanByAnnotation() {
	assertThat(coldBeer.getName()).isEqualTo("Beer");
}

Note: You might wonder how it is possible that the @Alcoholic  has a precedence over the primary bean. It's possible due to the @Alcoholic annotation. As you can see above, this annotation contains @Qualifier  annotation. Therefore, the @Alcoholic annotation is considered as a qualifier itself.

In the next part, we are going to check the injection of beans.

Injecting Collection of Beans

Now we get to the core part of this series. The injection of the beans is not that common, but it’s very useful in some cases. I use it a lot in order to separate concerns (when I need a "plugin" solution). An example of this approach can be a feature providing some general functionality (e.g. handling generation of XML/JSONs, handling notifications, etc.) on one side and the specific implementation for this feature on the other side (e.g. to generate XML/JSON from a specific entity, process a specific notification event, etc.).

Injecting by Type

As usual, we can inject a collection (i.e. Collection, List, or Set) of beans. An example of this injection is injecting all available beans implementing the Beverage interface. The shouldWireAllBeverages test method checks we have got all available beans from the Spring context.

Java
 
class BeverageCollectionWiringTest {
	
	@Autowired
	private Collection<Beverage> beverages;

	@Test
	void shouldWireAllBeverages() {
		assertThat(beverages).hasSize(6);
		assertThat(beverages)
          .map(Beverage::getName)
          .contains("Beer", "Cola", "Soda", "Coffee", "Tea", "Ice Tea");
	}

}

We can go further and limit the injected beans to the beans with their class extending AbstractCarbonatedBeverage class:

Java
 
@Autowired
private Collection<? extends AbstractCarbonatedBeverage> carbonatedBeverages;

@Test
void shouldWireCarbonatedBeverages() {
	assertThat(carbonatedBeverages).hasSize(3);
	assertThat(carbonatedBeverages).map(Beverage::getName).contains("Beer", "Cola", "Soda");
}

Note: We can also define the carbonatedBeverages property as Collection<AbstractCarbonatedBeverage>. It works both ways.

We can also inject the desired beans as an array. It's just about our preference or a later usage in the code.

Java
 
@Autowired
private HotBeverage[] hotBeverages;

@Test
void shouldWireHotBeverages() {
	assertThat(hotBeverages).hasSize(2);
	assertThat(asList(hotBeverages)).map(Beverage::getName).contains("Coffee", "Tea");
}

Injection by Annotation

Our last option here represents injecting beans marked with an annotation (usually the custom one). We have the @Alcoholic annotation to demonstrate this approach.

Java
 
@Autowired
@Alcoholic
private Collection<Beverage> alcoholicBeverages;

@Test
void shouldWireAlcoholicBeverages() {
	assertThat(alcoholicBeverages).hasSize(1);
	assertThat(alcoholicBeverages).map(Beverage::getName).contains("Beer");
}

Note: We have only one alcoholic beverage here, but it works for this as well.

Injecting Map of Beans

Finally, we get to the injection of a map of beans. It works in the exact same way as injecting a collection of beans. The difference is that we also have a bean name (the qualifier of the bean). An example of wiring all carbonated beverages looks like this:

Java
 
@SpringBootTest(classes = WiringConfig.class)
class BeverageMapWiringTest {
	
	@Autowired
	private Map<String, ? extends AbstractCarbonatedBeverage> carbonatedBeverages;

	@Test
	void shouldWireCarbonatedBeverages() {
		assertThat(carbonatedBeverages)
				.hasSize(3)
				.containsKeys("beer", "cola", "soda");
	}

}

Note: There’s no sense to show you all available examples as they are similar to the previous ones. However, you can find all examples in my GitHub repository.

Conclusion

This article has covered DI with assignability. I began with wiring a single bean by interface, abstract class, or any of these combined with an annotation. Next, I presented the very same approach for the collection of beans. In the end, I also provided an example for injecting a map of beans. The complete source code demonstrated above is available in my GitHub repository.

In the next article, I will focus on DI with generics.

Spring Framework Dependency injection Java (programming language) Annotation Interface (computing)

Opinions expressed by DZone contributors are their own.

Related

  • Dependency Injection in Spring
  • Mastering Spring: Synchronizing @Transactional and @Async Annotations With Various Propagation Strategies
  • Composing Custom Annotations in Spring
  • Java, Spring Boot, and MongoDB: Performance Analysis and Improvements

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!