Efficient Service Mapping with Spring's Auto Injection
Written on
Chapter 1: Introduction to Automatic Service Mapping
This article delves into a method for automatically creating a map of services, with service names as keys and their corresponding instances as values. This approach serves as a modern alternative to the conventional factory pattern, and its utility becomes evident through examples. Let’s explore this concept further.
Use Case
The source code referenced in this article can be accessed on GitHub [here](#).
Consider a scenario where multiple concrete classes derive from a shared abstract base class. The class hierarchy is structured as follows:
Each concrete class is tasked with specific functionalities. The entry point for each class is defined by abstract methods in the base service. Thus, any variable of type BaseService guarantees that the implementing class has defined these methods.
Some practical applications for this setup include:
- Event Handlers: Each service class manages a distinct type of event.
- File Processing: Services handle various identifiers, entities, or sections within a file.
- User Interaction: Classes manage different user interactions within the system, akin to event handlers, but user-initiated.
Example Class Structure
An example of how these classes might be structured is as follows:
public abstract class BaseService {
public abstract String printAndGetGreeting(String value);
protected void print(final String message) {
System.out.println(message);}
}
In this example, we define one abstract method, printAndGetGreeting, alongside a concrete method called print.
Concrete Class Implementations
Implementations of this abstract class can be illustrated as follows:
@Service(ServiceConstants.TYPEA)
public class TypeAService extends BaseService {
@Override
public String printAndGetGreeting(String value) {
String message = "From service type A: " + value;
print(message);
return message;
}
}
Note the service annotation, which includes a string identifier for the service type, allowing for custom naming.
Additional subclasses could be structured similarly:
@Service(ServiceConstants.TYPEB)
public class TypeBService extends BaseService {
@Override
public String printAndGetGreeting(String value) {
String message = "From service type B: " + value;
print(message);
return message;
}
}
@Service(ServiceConstants.TYPEC)
public class TypeCService extends BaseService {
@Override
public String printAndGetGreeting(String value) {
String message = "From service type C: " + value;
print(message);
return message;
}
}
Chapter 2: Traditional Factory Pattern
To illustrate a conventional approach to service management, consider the following factory class:
@Service
@AllArgsConstructor
public class ServiceFactory {
private final TypeAService typeAService;
private final TypeBService typeBService;
private final TypeCService typeCService;
public BaseService getService(final String serviceName) {
if (ServiceConstants.TYPEA.equalsIgnoreCase(serviceName)) {
return typeAService;} else if (ServiceConstants.TYPEB.equalsIgnoreCase(serviceName)) {
return typeBService;} else {
return typeCService;}
}
}
This factory class autowires each service, utilizing Lombok’s @AllArgsConstructor and declaring services as private final. The getService method then returns the corresponding service based on the provided name.
Although this method is effective, it has its downsides. For instance, if we wish to add a new service class, such as TypeDService, we must undertake three repetitive steps, leading to code bloat.
Chapter 3: Streamlined Service Injection
Imagine a more efficient service factory structured as follows:
@Service
@AllArgsConstructor
public class MainService {
// This map is automatically filled by Spring
private final Map<String, BaseService> serviceMap;
public BaseService getService(final String serviceName) {
return serviceMap.get(serviceName);}
}
This version incorporates a map that automatically receives entries from Spring, where the keys correspond to service names.
Upon instantiation, the map populates itself with all classes implementing BaseService, with the keys derived from the @Service annotation. This results in a straightforward lookup method without cumbersome conditional statements.
To add a new service like TypeDService, you only need to implement the class, and it will automatically be included in the map, streamlining the process significantly.
This technique shines particularly in scenarios with evolving requirements, such as event handling or processing different file types. Frequent additions of services become hassle-free.
For hands-on experience, you can download the complete source code from GitHub and explore the provided REST endpoints for both traditional and auto-injected methods.
Endpoints for Testing
- Traditional: http://localhost:8080/api/old/map?service=typea&message=hello%20from%20A
- Auto Injection: http://localhost:8080/api/map?service=typea&message=hello%20from%20A
Conclusion
This article introduced a valuable Spring technique for creating an auto-injectable service map. We compared the traditional factory method with the auto-injection approach, highlighting scenarios where this method excels. This technique is a worthy addition to any developer's toolkit when dealing with dynamic requirements.
The complete code is available on my [GitHub](#).
Enjoy exploring this method!
🔔 If you found this insightful, consider subscribing for future articles or checking out my previously published works. 🚀
📝 Have any questions, suggestions, or topics you'd like to discuss? Feel free to comment or reach out through Medium.
Thank you for your support! 🌟
Discover best practices for dependency injection in Spring through this comprehensive video.
Learn about machine learning-based service mapping in this informative video.