Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BAEL-7868] Autowiring an interface with multiple implementations #16635

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions spring-di-4/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
<mainClass>com.baeldung.registrypostprocessor.RegistryPostProcessorApplication</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.baeldung.autowiremultipleimplementations.candidates;

public interface GoodService {

String getHelloMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.baeldung.autowiremultipleimplementations.candidates;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

@Service
@Profile("default")
@Qualifier("goodServiceA-custom-name")
@Order(3)
public class GoodServiceA implements GoodService {

@Override
public String getHelloMessage() {
return "Hello from A!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.baeldung.autowiremultipleimplementations.candidates;

import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

@Service
@Profile("default")
@Order(2)
public class GoodServiceB implements GoodService {

@Override
public String getHelloMessage() {
return "Hello from B!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.baeldung.autowiremultipleimplementations.candidates;

import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

@Primary
@Service
@Profile("default")
@Order(1)
public class GoodServiceC implements GoodService {

@Override
public String getHelloMessage() {
return "Hello from C!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.baeldung.autowiremultipleimplementations.candidates;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Service
@Profile("dev")
public class GoodServiceD implements GoodService {

public String getHelloMessage() {
return "Hello from D!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.baeldung.autowiremultipleimplementations.candidates;

import com.baeldung.autowiremultipleimplementations.condition.OnFeatureEnabledCondition;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;

@Service
@Conditional(OnFeatureEnabledCondition.class)
public class GoodServiceE implements GoodService {

public String getHelloMessage() {
return "Hello from E!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.baeldung.autowiremultipleimplementations.components;

import com.baeldung.autowiremultipleimplementations.candidates.GoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Set;

@Component
public class CollectionsAutowire {

private final Set<GoodService> goodServices;
private final Map<String, GoodService> goodServiceMap;

@Autowired
public CollectionsAutowire(Set<GoodService> goodServices, Map<String, GoodService> goodServiceMap) {
this.goodServices = goodServices;
this.goodServiceMap = goodServiceMap;
}

public String hello() {
return goodServices.stream()
.map(GoodService::getHelloMessage)
.reduce((a, b) -> a + " " + b)
.orElse("No services available");
}

public String helloMap() {
return goodServiceMap.values().stream()
.map(GoodService::getHelloMessage)
.reduce((a, b) -> a + " " + b)
.orElse("No services available");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.baeldung.autowiremultipleimplementations.components;

import com.baeldung.autowiremultipleimplementations.candidates.GoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class PrimaryAutowire {

private final GoodService goodService;

@Autowired
public PrimaryAutowire(GoodService goodService) {
this.goodService = goodService;
}

public String hello() {
return goodService.getHelloMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.baeldung.autowiremultipleimplementations.components;

import com.baeldung.autowiremultipleimplementations.candidates.GoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class QualifierAutowire {

private final GoodService goodServiceA;
private final GoodService goodServiceB;
private final GoodService goodServiceC;

@Autowired
public QualifierAutowire(@Qualifier("goodServiceA-custom-name") GoodService niceServiceA,
@Qualifier("goodServiceB") GoodService niceServiceB,
GoodService goodServiceC) {
this.goodServiceA = niceServiceA;
this.goodServiceB = niceServiceB;
this.goodServiceC = goodServiceC;
}

public String hello() {
return goodServiceA.getHelloMessage() + " " +
goodServiceB.getHelloMessage() + " " +
goodServiceC.getHelloMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.baeldung.autowiremultipleimplementations.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OnFeatureEnabledCondition implements Condition {

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String featureToggle = context.getEnvironment().getProperty("feature.toggle");
return "enabled".equalsIgnoreCase(featureToggle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.baeldung.autowiremultipleimplementations;

import com.baeldung.autowiremultipleimplementations.components.CollectionsAutowire;
import com.baeldung.autowiremultipleimplementations.candidates.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {CollectionsAutowire.class, GoodServiceD.class, GoodServiceC.class, GoodServiceB.class, GoodServiceA.class})
public class CollectionsAutowireUnitTest {

@Autowired
private CollectionsAutowire collectionsAutowire;

@Test
public void testSetAutowiring() throws NoSuchFieldException, IllegalAccessException {
Set<?> rawServices = (Set<?>) getPrivateField(collectionsAutowire, "goodServices");
assertNotNull(rawServices, "Set of GoodService should not be null");
assertFalse(rawServices.isEmpty(), "Set of GoodService should not be empty");

Set<GoodService> services = rawServices.stream()
.map(service -> (GoodService) service)
.filter(Objects::nonNull)
.collect(Collectors.toSet());

// Check for all specific types
assertTrue(services.stream().anyMatch(s -> s instanceof GoodServiceA), "Should contain GoodServiceA");
assertTrue(services.stream().anyMatch(s -> s instanceof GoodServiceB), "Should contain GoodServiceB");
assertTrue(services.stream().anyMatch(s -> s instanceof GoodServiceC), "Should contain GoodServiceC");

String actualMessage = collectionsAutowire.hello();
assertTrue(actualMessage.contains("Hello from A!"), "Message should contain greeting from A");
assertTrue(actualMessage.contains("Hello from B!"), "Message should contain greeting from B");
assertTrue(actualMessage.contains("Hello from C!"), "Message should contain greeting from C");
}

@Test
public void testMapAutowiring() throws NoSuchFieldException, IllegalAccessException {
Map<?, ?> rawServiceMap = (Map<?, ?>) getPrivateField(collectionsAutowire, "goodServiceMap");
assertNotNull(rawServiceMap, "Map of GoodService should not be null");
assertFalse(rawServiceMap.isEmpty(), "Map of GoodService should not be empty");

Map<String, GoodService> serviceMap = rawServiceMap.entrySet().stream()
.collect(Collectors.toMap(
entry -> (String) entry.getKey(),
entry -> (GoodService) entry.getValue()
));

// Check keys and specific instances
assertTrue(serviceMap.containsKey("goodServiceA"), "Map should contain a key for GoodServiceA");
assertTrue(serviceMap.containsKey("goodServiceB"), "Map should contain a key for GoodServiceB");
assertTrue(serviceMap.containsKey("goodServiceC"), "Map should contain a key for GoodServiceC");

assertInstanceOf(GoodServiceA.class, serviceMap.get("goodServiceA"), "goodServiceA should be an instance of GoodServiceA");
assertInstanceOf(GoodServiceB.class, serviceMap.get("goodServiceB"), "goodServiceB should be an instance of GoodServiceB");
assertInstanceOf(GoodServiceC.class, serviceMap.get("goodServiceC"), "goodServiceC should be an instance of GoodServiceC");
}

private Object getPrivateField(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.baeldung.autowiremultipleimplementations;

import com.baeldung.autowiremultipleimplementations.candidates.GoodService;
import com.baeldung.autowiremultipleimplementations.candidates.GoodServiceE;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.junit.jupiter.api.Assertions.assertNull;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {GoodServiceE.class})
@TestPropertySource(properties = {"feature.toggle=disabled"})
public class ConditionalDisabledUnitTest {

@Autowired(required = false)
private GoodService goodService;

@Test
void testServiceWhenFeatureDisabled() {
assertNull(goodService, "GoodService should not be autowired when feature.toggle is disabled");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.baeldung.autowiremultipleimplementations;

import com.baeldung.autowiremultipleimplementations.candidates.GoodService;
import com.baeldung.autowiremultipleimplementations.candidates.GoodServiceE;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {GoodServiceE.class})
@TestPropertySource(properties = {"feature.toggle=enabled"})
public class ConditionalEnabledUnitTest {

@Autowired
private GoodService goodService;

@Test
void testServiceWhenFeatureEnabled() {
assertNotNull(goodService, "GoodService should be autowired when feature.toggle is enabled");
assertInstanceOf(GoodServiceE.class, goodService, "goodService should be an instance of GoodServiceE");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.baeldung.autowiremultipleimplementations;

import com.baeldung.autowiremultipleimplementations.components.PrimaryAutowire;
import com.baeldung.autowiremultipleimplementations.candidates.GoodService;
import com.baeldung.autowiremultipleimplementations.candidates.GoodServiceA;
import com.baeldung.autowiremultipleimplementations.candidates.GoodServiceB;
import com.baeldung.autowiremultipleimplementations.candidates.GoodServiceC;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest(classes = {PrimaryAutowire.class, GoodServiceC.class, GoodServiceB.class, GoodServiceA.class})
@ExtendWith(SpringExtension.class)
public class PrimaryAutowireUnitTest {

@Autowired
private ApplicationContext context;

@Autowired
private PrimaryAutowire primaryAutowire;

@Test
public void whenPrimaryServiceInjected_thenItShouldBeGoodServiceC() {
GoodService injectedService = context.getBean(GoodService.class);

assertInstanceOf(GoodServiceC.class, injectedService);

String expectedMessage = "Hello from C!"; // GoodServiceC returns this message
String actualMessage = primaryAutowire.hello();

assertEquals(expectedMessage, actualMessage);
}
}