Applying the Service Locator Pattern in Spring to Decouple File Parsers
The article explains how to replace tightly‑coupled if‑else or switch statements for selecting CSV, JSON, or XML parsers with a Service Locator Pattern in Spring, showing step‑by‑step code examples, configuration, and the resulting adherence to the open‑closed principle.
When an application needs to parse different file types (e.g., CSV, JSON), developers often use if else or switch case statements to select the appropriate parser, which tightly couples the client to concrete implementations and forces code changes whenever a new type is added.
The Service Locator Pattern solves this problem by introducing a locator that returns the required service based on a key, thereby removing the direct dependency between the client and specific parser classes.
Example steps:
Define an enum ContentType with values JSON and CSV (later extended with XML ).
Create a Parser interface with a parse(Reader r) method.
Implement concrete parsers such as CSVParser and JSONParser that implement Parser .
public enum ContentType {
JSON,
CSV
} public interface Parser {
List parse(Reader r);
} // CSV parser
@Component
public class CSVParser implements Parser {
@Override
public List parse(Reader r) { /* ... */ }
}
// JSON parser
@Component
public class JSONParser implements Parser {
@Override
public List parse(Reader r) { /* ... */ }
}A naïve client would still use a switch case to choose the parser, which remains tightly coupled.
@Service
public class Client {
private Parser csvParser, jsonParser;
@Autowired
public Client(Parser csvParser, Parser jsonParser) {
this.csvParser = csvParser;
this.jsonParser = jsonParser;
}
public List getAll(ContentType contentType) {
switch (contentType) {
case CSV: return csvParser.parse(reader);
case JSON: return jsonParser.parse(reader);
}
return null;
}
}To eliminate the switch, define a ParserFactory interface that the Service Locator will implement:
public interface ParserFactory {
Parser getParser(ContentType contentType);
}Configure Spring’s ServiceLocatorFactoryBean to create the factory:
@Configuration
public class ParserConfig {
@Bean("parserFactory")
public FactoryBean serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
// set the service locator interface
factoryBean.setServiceLocatorInterface(ParserFactory.class);
return factoryBean;
}
}Annotate each parser bean with a name that matches the enum value:
// set bean name to match type
@Component("CSV")
public class CSVParser implements Parser { /* ... */ }
@Component("JSON")
public class JSONParser implements Parser { /* ... */ }
@Component("XML")
public class XMLParser implements Parser { /* ... */ }Extend the enum to include the new type:
public enum ContentType {
JSON,
CSV,
XML
}The client now depends only on ParserFactory and obtains the correct parser without any conditional logic:
@Service
public class Client {
private ParserFactory parserFactory;
@Autowired
public Client(ParserFactory parserFactory) {
this.parserFactory = parserFactory;
}
public List getAll(ContentType contentType) {
// directly retrieve the parser
return parserFactory.getParser(contentType).parse(reader);
}
}This approach follows the open‑closed principle: adding a new parser (e.g., XML) only requires creating the implementation and registering the bean, with no changes to the client code.
While the Service Locator can be useful for certain cases where dependency injection does not provide a clean solution, DI remains the preferred pattern in most Spring applications.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.