4.7 Service Locator
The Service Locator pattern provides a central registry through which clients obtain services by name, instead of creating or looking them up themselves. Its original motive: in J2EE, looking up a service (a data source, EJB, message queue) via JNDI was expensive. The Service Locator caches the service after the first lookup, so later calls hit the cache and avoid repeating the costly lookup.
In the lab below, getting a service the first time goes through an "expensive JNDI lookup," while the second time hits the cache and returns instantly.
Registry + cache
class ServiceLocator {
private static Map<String, Service> cache = new HashMap<>();
public static Service get(String name) {
if (cache.containsKey(name)) return cache.get(name); // cache hit
Service svc = new InitialContext().lookup(name); // expensive lookup, once
cache.put(name, svc);
return svc;
}
}The client only needs ServiceLocator.get("OrderService"), caring nothing about how the service was looked up, created, or cached. The lab below is a JNDI lookup simulator that lets you locate different service instances by name.
The debate: Service Locator vs Dependency Injection
This is a famous debate in modern design. Both solve "how does an object obtain the services it depends on," but in opposite directions:
- Service Locator: the object actively asks the locator for dependencies (
locator.get(...)).
- Dependency Injection (DI): dependencies are passively injected into the object (via constructor/setter).
Many people (including Martin Fowler) prefer DI, arguing that the Service Locator hides dependencies — you can't tell from a class's constructor signature what it actually depends on; you must read its implementation, which weakens testability and visibility.
Still, the Service Locator isn't worthless: where you can't control object creation (some framework callbacks, legacy systems) or need to resolve services dynamically at runtime, it remains useful. Understanding this debate helps you make a justified tradeoff in real projects.