Spring DI. Multiple Interface Implementations with Strategy Pattern
One of the remarkable features of the Spring Framework is its ability to manage multiple implementations of the same interface, selecting the necessary implementation based on the execution context. Let’s see how this can be effectively applied in practice.
Hi, this is Paul, and welcome to my Spring guide. Today we will discuss how Dependency Injection in the Spring Framework works for multiple implementations of one interface.
Example: Data Conversion Service
Imagine we have a service for converting data from one format to another, for example, from JSON to PDF or DOC formats. We implement this using the strategy pattern, where each strategy corresponds to a specific output format.
Dependency Autowiring with @Component and @Autowired
The Spring Framework offers a convenient way to manage dependencies through autowiring. For each conversion strategy, we define a separate component:
public interface ReportConversionStrategy {
byte[] convertReport(ReportDto report);
}
@Component("PDF")
public class PdfConversionStrategy implements ReportConversionStrategy {
@Override
public byte[] convertReport(ReportDto report) {
return new byte[0];
}
}
@Component("DOC")
public class DocConversionStrategy implements ReportConversionStrategy {
@Override
public byte[] convertReport(ReportDto report) {
return new byte[0];
}
}
Using the @Autowired
annotation, Spring automatically injects all available implementations of ReportConversionStrategy
into our service, allowing us to dynamically select the necessary strategy based on input parameters.
Implementation of the Conversion Service
The conversion service integrates strategies and provides a universal interface for report conversion:
@Service
public class ReportConversionService {
private final Map<String, ReportConversionStrategy> strategies = new HashMap<>();
@Autowired
public ReportConversionService(List<ReportConversionStrategy> strategies) {
strategies.forEach(strategy ->
this.strategies.put(strategy.getClass().getAnnotation(Component.class).value(), strategy));
}
public byte[] convertReport(String format, ReportDto report) {
ReportConversionStrategy strategy = strategies.get(format.toUpperCase());
if (strategy == null) {
throw new UnsupportedOperationException("Unsupported format: " + format);
}
return strategy.convertReport(report);
}
}
In this service, we use a map to link the format with the corresponding conversion strategy, allowing us to easily expand supported formats and implementations without altering the core logic of the service.
Conclusion
The application of the dependency autowiring mechanism in Spring, combined with the strategy pattern, demonstrates how to flexibly and efficiently manage different implementations of interfaces. This approach not only simplifies the addition of new features and code maintenance but also enhances its readability and improves the overall architecture of the application, making it more adaptive and extensible.
Thank you for reading until the end. Before you go:
- Please consider clapping and following the writer! 👏
- Follow us on Twitter(X), LinkedIn