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

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Smart Dependency Injection With Spring: Assignability (Part 2 of 3)
  • Smart Dependency Injection With Spring: Overview (Part 1 of 3)
  • Introduction to Apache Kafka With Spring
  • Dependency Injection Implementation in Core Java

Trending

  • A Modern Stack for Building Scalable Systems
  • Microsoft Azure Synapse Analytics: Scaling Hurdles and Limitations
  • Unlocking AI Coding Assistants Part 4: Generate Spring Boot Application
  • A Guide to Developing Large Language Models Part 1: Pretraining
  1. DZone
  2. Coding
  3. Frameworks
  4. Smart Dependency Injection With Spring - Generics (Part 3/3)

Smart Dependency Injection With Spring - Generics (Part 3/3)

This article is the last one in my mini-series dedicated to dependency injection with the Spring framework. Using generics to simplify injecting beans with Spring framework.

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

Join the DZone community and get the full member experience.

Join For Free

Preface

The Spring framework is a powerful framework that provides first-class support for dependency injection (DI). This article is the last one in my mini-series dedicated to dependency injection with Spring Framework. 

This series is split into three articles:

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

In this article, you will learn:

  • How to inject a single bean defined with a generic class
  • How to inject collection or map of beans defined with a generic class
  • Some useful tips and issues related to injecting beans with a generic class

Overview

In my previous articles in this series, I reviewed the DI basics and the injection by assignable type. Here, we shed some light on the injection of beans defined by a generic class. For this topic, we need another set of classes. Let's start with them first.

Order Domain

In the last article, we use a beverage domain. This domain re-uses all the classes from the beverage domain. This domain is very simple and straightforward. The relationship between classes and their relationship is depicted below. 

The class hierarchy of the beverage domain

BeverageOrder Interface

The root  element in the order domain is a BeverageOrder interface with a single method called takeOrder. The goal of this method is to accept a beverage bean for order and return some specific message to the handled beverage.

Java
 
public interface BeverageOrder<T extends Beverage> {

	String takeOrder(T beverage);
	
}


Note: this is a very artificial example serving just for the purpose of this article.

TeaOrder Class

The first class implementing the BeverageOrder interface is the TeaOrder class defined as:

Java
 
@Component
public class TeaOrder implements BeverageOrder<Tea> {

	public String takeOrder(Tea beverage) {
		return beverage.getName() + " order is taken.";
	}
	
}


Note: the TeaOrder class uses the Tea class (from the beverage domain) to accept an order.

Additionally, we can also define beans in JavaConfig as well. The sodaOrder bean is an example of this approach.

Java
 
@SpringBootApplication
public class WiringConfig {

	@Bean
	public BeverageOrder<Soda> sodaOrder() {
		return beverage -> beverage.getName() + " is ready to be served.";
	}

}


This is all for our order domain. Next, we start with the examples of using these classes for the injection.

Injecting Single Bean

You should be familiar with injecting a single bean now. Here, we just modify our previous examples for injection to use the generic classes.

Note: you can check e.g. these articles if you need to refresh the generics topic:

  • https://www.baeldung.com/java-generics
  • https://docs.oracle.com/javase/tutorial/java/generics/types.html

Injection by Name

The common way to inject a bean (of course except for the injection by the type) is the injection by the name. Such injection with generics behaves in the same way as before (simple type or assignable type). When you have more beans of the same type (e.g.  more classes extending the AbstractCarbonatedBeverage class) then we can inject the desired bean instance like this (line 5):

Java
 
@SpringBootTest(classes = WiringConfig.class)
class OrderSingleWiringTest {

	@Autowired
	private BeverageOrder<? extends AbstractCarbonatedBeverage> sodaOrder;

	@Test
	void shouldWireBeanByName() {
		assertThat(((BeverageOrder<Soda>) sodaOrder).takeOrder(new Soda())).isEqualTo("Soda is ready to be served.");
	}

}


As usual, we can verify the injection by checking the returned message from the order (line 9).

Note: we cannot simply inject BeverageOrder<AbstractCarbonatedBeverage>. More information related to this issue can be found at the end (the final note part).

Injection of Primary Bean

The injection of a bean with the @Primary annotation works exactly in the same way as in the previous cases (simple type or assignable type), because the generics cannot change the primary bean principle. In other words, we don't need to treat the generics in a special way. You can find the example here.

Injection by Qualifier

As usual, we can inject any bean by its qualifier value (line 2).

Java
 
@Autowired
@Qualifier("colaOrder")
private BeverageOrder<? extends AbstractCarbonatedBeverage> beverageOrder;

@Test
void shouldWireBeanByQualifier() {
	assertThat(((BeverageOrder<Cola>) beverageOrder).takeOrder(new Cola())).isEqualTo("Cola is temporarily not available.");
}


To make it work, we need to tell the correct type to the compiler. Either cast the argument as <T extends AbstractCarbonatedBeverage> or cast the order bean to the passed bean type. We use the latter option here.

Note: honestly, the generic class has no real value here. We can easily define beverageOrder like BeverageOrder<?> or BeverageOrder with the same result. The qualifier always wins once the type is assignable.

Injection of Generic Type

More interesting is the injection by the generic type (line 2). It works similarly to the qualifier, but we use the generic class instead of the qualifier.

Java
 
@Autowired
private BeverageOrder<Tea> teaOrder;

Test
void shouldWireBeanByType() {
	assertThat(teaOrder.takeOrder(new Tea())).isEqualTo("Tea order is taken.");
}


Injecting Collection of Beans

The injection of a collection of beans defined with the generic class has fewer options than the injection of a collection of assignable beans. The injection by the qualifier, the name, or the primary bean doesn't make sense in this case. We talk about several beans here. Therefore, we cannot apply principles for the single bean. 

Moreover, the injection by an annotated type doesn't work. Note: more information about this issue can be found at the end (the final note part). Therefore, we can inject a collection of beans with generics only by their type.

Injecting All Orders

In order to inject all available orders (no matter what beverage they are purposed for), we should inject them as Collection<BeverageOrder<?>> (line 10) or any other shared ancestor class.

Java
 
@SpringBootTest(classes = WiringConfig.class)
class OrderCollectionWiringTest {
	
	/**
	 * The exact name cannot be predicted as it's derived from lambda.
	 */
	private static final String SODA_ORDER = "WiringConfig$$Lambda$";

	@Autowired
	private Collection<BeverageOrder<?>> allOrders;

	@Test
	void shouldWireAllOrders() {
		assertThat(allOrders).hasSize(4);
		assertThat(allOrders).map(BeverageOrder::getClass).map(Class::getSimpleName)
				.contains("BeerOrder", "ColaOrder", "TeaOrder")
				.anyMatch(c -> c.startsWith(SODA_ORDER));
	}

}


The verification is done as usual by the shouldWireAllOrders test method. The interesting part here is when we want to check the class name for the bean defined in JavaConfig (sodaOrder). Such classes don't have a known class in advance. The class is generated at runtime. Therefore, we cannot simply check sodaOrder class as in other cases. In our case, we use a workaround and simply check sodaOrder against SODA_ORDER value (as it's the only bean defined like this).

Note: we can modify the injection also to type Collection<BeverageOrder> or Collection<BeverageOrder<? extends Beverage>>, but never as Collection<BeverageOrder<Beverage>>. The explanation is mentioned at the end of this article.

Injecting Specific Orders

As we have seen many times before, we can reduce the number of injected beans. This time, we want all orders to accept only carbonated beverages. It can be achieved by putting a constraint on the generic class. The example below uses ? extends AbstractCarbonatedBeverage type to inject all beans handling order for a carbonated beverage (line 2).

Java
 
@Autowired
private BeverageOrder<? extends AbstractCarbonatedBeverage>[] carbonatedOrders;

@Autowired
private BeverageOrder<Beer> beerOrder;

@Autowired
private BeverageOrder<Cola> colaOrder;

@Autowired
private BeverageOrder<Soda> sodaOrder;

@Test
void shouldWireAllCarnonatedOrders() {
	assertThat(carbonatedOrders)
			.hasSize(3)
			.contains(beerOrder, colaOrder, sodaOrder);
}


Note: the verification is done against whole beans this time. Therefore we need to inject all expected beans by their type (see lines 4-11).

Injecting Map of Beans

The last example shows the injection of a map of beans defined with a generic class. It works in the same way as injecting a collection of beans. An example of wiring all carbonated beverages can look like this (line 2): 

Java
 
@Autowired
private Map<String, BeverageOrder<? extends AbstractCarbonatedBeverage>> carbonatedOrders;

@Test
void shouldWireAllOrders() {
	assertThat(carbonatedOrders)
			.hasSize(3)
			.containsKeys("beerOrder", "colaOrder", "sodaOrder");
}


Final Notes

Now, we know how to use the dependency injection with the generics in Spring Framework. However, there are a couple of issues we should be aware of. 

Using the Raw Type

We need to define the injected type precisely. The goal is to allow Spring to find the exact match of our type and the existing beans in its context. Do you think the injection of the BeverageOrder<AbstractCarbonatedBeverage> would work?

Java
 
@Autowired
private BeverageOrder<AbstractCarbonatedBeverage> sodaOrder;


Unfortunately no, because the Spring content doesn't have any bean of the type AbstractCarbonatedBeverage. There are several descendants, but none of them is defined just but this type. Therefore, we get NoSuchBeanDefinitionException exception when we try to inject this type.

Plain Text
 
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.github.aha.sat.core.wiring.order.BeverageOrder<com.github.aha.sat.core.wiring.beverage.AbstractCarbonatedBeverage>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657)
	... 74 common frames omitted


Casting

When we inject some beans with the generic type using wildcard (?), we obviously want to use them in some general way. Otherwise, we can inject them with the specific type, cannot we?

For example, when we use the extended type <? extends AbstractCarbonatedBeverage> then we want to trigger some of its methods then we need to take care of the casting. It's because the compiler is not capable of doing it on its own. To make it work, we need to specify the correct type to the compiler. Either cast the argument as <T extends AbstractCarbonatedBeverage> (argument side) or cast the processing bean to accept the passed bean type (provider side).

Argument Side

In order to cast our bean to the desired type, we can introduce e.g. castType method (lines 9-11) returning the expected T extends AbstractCarbonatedBeverage type. This makes the compiler happy.

Java
 
@Autowired
private BeverageOrder<? extends AbstractCarbonatedBeverage> sodaOrder;

@Test
void shouldWireBeanByName() {
	assertThat(sodaOrder.takeOrder(castType(new Soda()))).isEqualTo("Soda is ready to be served.");
}

<T extends AbstractCarbonatedBeverage> T castType(AbstractCarbonatedBeverage b) {
	return (T) b;
}


Provider Side

An example of the provider side example was used in our examples above (in order to make our examples simple). Here, we need to cast sodaOrder bean to accept the BeverageOrder<Soda> type.

Java
 
assertThat(((BeverageOrder<Soda>) sodaOrder).takeOrder(new Soda())).isEqualTo("Soda is ready to be served.");


Injection by Annotation

In several cases, we could see the injection based on an annotation specified in the injected bean class. Unfortunately, it doesn't work with generics. Please, let me know if you have any working examples.

Java
 
@Autowired
private BeverageOrder<@Alcoholic ? extends AbstractCarbonatedBeverage> beerOrderBean;


Note: the property name beerOrderBean is intentional. If we use the name beerOrder then we will get the correct instance injected by the name.

Conclusion

This article has covered DI with generics. I began the article with the demonstration of different options to inject a single bean with a generic class. Next, I explained the injection of collection and map of beans with generics. In the end, I provided some hints and known issues related to DI with generics.

This is the last article in my mini-series dedicated to the smart dependency injection with Spring Framework. I hope you enjoyed it and found at least some helpful information. As usual, the complete source code presented above is available in my GitHub repository.

Dependency injection Spring Framework

Opinions expressed by DZone contributors are their own.

Related

  • Smart Dependency Injection With Spring: Assignability (Part 2 of 3)
  • Smart Dependency Injection With Spring: Overview (Part 1 of 3)
  • Introduction to Apache Kafka With Spring
  • Dependency Injection Implementation in Core Java

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!