diff --git a/README.md b/README.md index 46d487bb9..6c82be44e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ * [Facade](#facade) * [Flyweight](#flyweight) * [Proxy](#proxy) + * [Service Locator](#service-locator) * Behavioral Patterns * [Chain of responsibility](#chain-of-responsibility) * [Command](#command) @@ -174,6 +175,18 @@ * facilitate network connection * to count references to an object +## Service Locator [↑](#list-of-design-patterns) +**Intent:** Encapsulate the processes involved in obtaining a service with a strong abstraction layer. + +![alt text](https://github.com/iluwatar/java-design-patterns/blob/master/service-locator/etc/service-locator.png "Proxy") + +**Applicability:** The service locator pattern is applicable whenever we want to locate/fetch various services using JNDI which, typically, is a redundant and expensive lookup. The service Locator pattern addresses this expensive lookup by making use of caching techniques ie. for the very first time a particular service is requested, the service Locator looks up in JNDI, fetched the relavant service and then finally caches this service object. Now, further lookups of the same service via Service Locator is done in its cache which improves the performance of application to great extent. + +**Typical Use Case:** + +* When network hits are expensive and time consuming +* lookups of services are done quite frequently +* large number of services are being used ## Chain of responsibility [↑](#list-of-design-patterns) **Intent:** Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. diff --git a/pom.xml b/pom.xml index 1e6b6e98c..1a0a0c9b7 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,7 @@ visitor double-checked-locking servant + service-locator diff --git a/service-locator/etc/model.ucls b/service-locator/etc/model.ucls new file mode 100644 index 000000000..bf17e5eba --- /dev/null +++ b/service-locator/etc/model.ucls @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/service-locator/etc/service-locator.png b/service-locator/etc/service-locator.png new file mode 100644 index 000000000..72016b972 Binary files /dev/null and b/service-locator/etc/service-locator.png differ diff --git a/service-locator/pom.xml b/service-locator/pom.xml new file mode 100644 index 000000000..a7cb56f8e --- /dev/null +++ b/service-locator/pom.xml @@ -0,0 +1,9 @@ + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.0-SNAPSHOT + + service-locator + \ No newline at end of file diff --git a/service-locator/src/main/java/com/iluwater/App.java b/service-locator/src/main/java/com/iluwater/App.java new file mode 100644 index 000000000..9905b68df --- /dev/null +++ b/service-locator/src/main/java/com/iluwater/App.java @@ -0,0 +1,20 @@ +package com.iluwater; +/** + * Service locator pattern, used to lookup jndi services + * and cache them for subsequent requests. + * @author saifasif + * + */ +public class App { + public static void main(String[] args) { + Service service = ServiceLocator.getService("jndi/serviceA"); + service.execute(); + service = ServiceLocator.getService("jndi/serviceB"); + service.execute(); + service = ServiceLocator.getService("jndi/serviceA"); + service.execute(); + service = ServiceLocator.getService("jndi/serviceA"); + service.execute(); + } + +} diff --git a/service-locator/src/main/java/com/iluwater/InitContext.java b/service-locator/src/main/java/com/iluwater/InitContext.java new file mode 100644 index 000000000..8c6e0f8d1 --- /dev/null +++ b/service-locator/src/main/java/com/iluwater/InitContext.java @@ -0,0 +1,29 @@ +package com.iluwater; + +/** + * For JNDI lookup of services from the web.xml. Will match name of the service name that + * is being requested and return a newly created service object with the name + * @author saifasif + * + */ +public class InitContext { + + /** + * Perform the lookup based on the service name. The returned object will need to be + * casted into a {@link Service} + * @param serviceName + * @return + */ + public Object lookup(String serviceName){ + if( serviceName.equals("jndi/serviceA") ){ + System.out.println("Looking up service A and creating new serivce for A"); + return new ServiceImpl("jndi/serviceA"); + } else if( serviceName.equals("jndi/serviceB") ){ + System.out.println("Looking up service B and creating new serivce for B"); + return new ServiceImpl("jndi/serviceB"); + } else { + return null; + } + } + +} diff --git a/service-locator/src/main/java/com/iluwater/Service.java b/service-locator/src/main/java/com/iluwater/Service.java new file mode 100644 index 000000000..cda1a9d7e --- /dev/null +++ b/service-locator/src/main/java/com/iluwater/Service.java @@ -0,0 +1,29 @@ +package com.iluwater; + +/** + * This is going to be the parent service interface which we will + * use to create our services. All services will have a + *
  • service name
  • + *
  • unique id
  • + *
  • execution work flow
  • + * @author saifasif + * + */ +public interface Service { + + /* + * The human readable name of the service + */ + public String getName(); + + /* + * Unique ID of the particular service + */ + public int getId(); + + /* + * The workflow method that defines what this service does + */ + public void execute(); + +} diff --git a/service-locator/src/main/java/com/iluwater/ServiceCache.java b/service-locator/src/main/java/com/iluwater/ServiceCache.java new file mode 100644 index 000000000..ec727126b --- /dev/null +++ b/service-locator/src/main/java/com/iluwater/ServiceCache.java @@ -0,0 +1,46 @@ +package com.iluwater; + +import java.util.HashMap; +import java.util.Map; + +/** + * The service cache implementation which will cache services that are being created. + * On first hit, the cache will be empty and thus any service that is being requested, will be + * created fresh and then placed into the cache map. On next hit, if same service name will + * be requested, it will be returned from the cache + * @author saifasif + * + */ +public class ServiceCache { + + private Map serviceCache; + + public ServiceCache() { + serviceCache = new HashMap(); + } + + /** + * Get the service from the cache. null if no service is found matching the + * name + * @param serviceName + * @return {@link Service} + */ + public Service getService(String serviceName){ + Service cachedService = null; + for (String serviceJndiName : serviceCache.keySet()){ + if( serviceJndiName.equals( serviceName ) ){ + cachedService = serviceCache.get(serviceJndiName); + System.out.println("(cache call) Fetched service " + cachedService.getName() + "("+cachedService.getId()+") from cache... !"); + } + } + return cachedService; + } + + /** + * Adds the service into the cache map + * @param newService + */ + public void addService(Service newService){ + serviceCache.put(newService.getName(), newService); + } +} diff --git a/service-locator/src/main/java/com/iluwater/ServiceImpl.java b/service-locator/src/main/java/com/iluwater/ServiceImpl.java new file mode 100644 index 000000000..335ebef78 --- /dev/null +++ b/service-locator/src/main/java/com/iluwater/ServiceImpl.java @@ -0,0 +1,37 @@ +package com.iluwater; + +/** + * This is a single service implementation of a sample service. This is the actual + * service that will process the request. The reference for this service is to + * be looked upon in the JNDI server that can be set in the web.xml deployment descriptor + * @author saifasif + * + */ +public class ServiceImpl implements Service { + + private String serviceName; + private int id; + + public ServiceImpl(String serviceName) { + // set the service name + this.serviceName = serviceName; + + // Generate a random id to this service object + this.id = (int)Math.floor(Math.random()*1000)+1; + } + + @Override + public String getName() { + return serviceName; + } + + @Override + public int getId() { + return id; + } + + @Override + public void execute() { + System.out.println("Service " + getName() + " is now executing with id " + getId()); + } +} diff --git a/service-locator/src/main/java/com/iluwater/ServiceLocator.java b/service-locator/src/main/java/com/iluwater/ServiceLocator.java new file mode 100644 index 000000000..e2fbae2b8 --- /dev/null +++ b/service-locator/src/main/java/com/iluwater/ServiceLocator.java @@ -0,0 +1,37 @@ +package com.iluwater; + +/** + * The service locator module. + * Will fetch service from cache, otherwise creats a fresh service and update cache + * + * @author saifasif + * + */ +public class ServiceLocator { + + private static ServiceCache serviceCache = new ServiceCache(); + + /** + * Fetch the service with the name param from the cache first, + * if no service is found, lookup the service from the {@link InitContext} and + * then add the newly created service into the cache map for future requests. + * @param serviceJndiName + * @return {@link Service} + */ + public static Service getService(String serviceJndiName){ + Service serviceObj = serviceCache.getService(serviceJndiName); + if ( serviceObj != null ){ + return serviceObj; + } else { + /* + * If we are unable to retrive anything from cache, then + * lookup the service and add it in the cache map + */ + InitContext ctx = new InitContext(); + serviceObj = (Service) ctx.lookup(serviceJndiName); + serviceCache.addService(serviceObj); + return serviceObj; + } + } + +}