J2EE Patterns: Service Locator.The Service Locator pattern is used when we want to locate various services using JNDI lookup.Considering the high cost of looking up a service (network latency, database hits), the Service Locator pattern makes use of Caching techniques.For the first time a service is requested, the Service Locator looks it up in the database or directory and caches the object. For all subsequent requests, it returns the object from the cache, improving performance.
Service Locator:The single point of contact for the client. It abstracts the lookup logic and manages the cache.
Initial Context:Creates the reference to the JNDI (Java Naming and Directory Interface) lookup. This is the expensive operation we want to avoid repeating.
Cache:A simple map or list to store references to services that have already been found.
Service:The actual concrete implementation (e.g., EJBService, JMSService) that the client wants to use.
Without Locator: You call the operator (Initial Context) every single time, pay a fee, ask for the number, and then write it down. This is slow and expensive.
With Locator:
You call the operator once.
You write the number in your personal Address Book (Cache).
Next time you need the plumber, you just look in your book. You don’t call the operator again.
public class Service1 implements Service { public void execute() { System.out.println("Executing Service1"); } public String getName() { return "Service1"; }}
Service2.java
Copy
public class Service2 implements Service { public void execute() { System.out.println("Executing Service2"); } public String getName() { return "Service2"; }}
public class InitialContext { public Object lookup(String jndiName) { if (jndiName.equalsIgnoreCase("SERVICE1")) { System.out.println("Looking up and creating a new Service1 object"); return new Service1(); } else if (jndiName.equalsIgnoreCase("SERVICE2")) { System.out.println("Looking up and creating a new Service2 object"); return new Service2(); } return null; }}
public class ServiceLocator { private static Cache cache; static { cache = new Cache(); } public static Service getService(String jndiName) { // 1. Try to get from Cache Service service = cache.getService(jndiName); if (service != null) { return service; } // 2. If not in Cache, lookup using Initial Context InitialContext context = new InitialContext(); Service newService = (Service) context.lookup(jndiName); // 3. Add to Cache for next time cache.addService(newService); return newService; }}
public class ServiceLocatorPatternDemo { public static void main(String[] args) { Service service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); // This should come from the cache, not a new lookup service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); }}
Output:
Copy
Looking up and creating a new Service1 objectExecuting Service1Looking up and creating a new Service2 objectExecuting Service2Returning cached Service1 objectExecuting Service1Returning cached Service2 objectExecuting Service2
Performance: Greatly improves network performance by caching expensive lookups.
Complexity: Adds many classes (Cache, Context, Locator) for simple logic.
Abstraction: Clients don’t need to know the complex JNDI syntax.
Hidden Dependencies: It makes unit testing harder because the dependencies are hidden inside the static ServiceLocator rather than passed in explicitly.
Service Locator asks: “Hey Locator, can you go find me the Service?”
Dependency Injection says: “Here is the Service you need. I already found it for you.”In Spring, the ApplicationContext acts as the Cache and Locator, but it is invisible to your code. You simply declare what you need, and Spring provides it.Old Way (Service Locator):
MyController.java
Copy
public class MyController { public void doWork() { // Explicit call to static locator (Hard to test!) MyService service = ServiceLocator.getService("MyService"); service.run(); }}
Modern Way (Spring DI):
MyController.java
Copy
@RestControllerpublic class MyController { // Spring injects the cached instance automatically private final MyService myService; public MyController(MyService myService) { this.myService = myService; } @GetMapping("/work") public void doWork() { myService.run(); }}
Spring Beans are Singleton by default, meaning they are cached automatically. You don’t need to write a custom Cache class anymore.