Skip to main content

Changing source code at run-time with Service Locator pattern

I have a service to get some data but the result can be different basing on where the implementation is. Technically, I have two or more concrete implementation of an interface and I am able to switch using these concrete classes at run-time. That means I have a place to configure it without re-deploying the application. In order to overcome this issue, I use Service Locator design pattern and here I only care about two advantages below:
  • Encapsulating the specific implementation, we just declare the name and don't care about the implementation of the service.
  • Changing the implementation at run-time.



Client: an object that invokes the services via Service Locator
Business services: services that is used by Client.


Once again, I used the JFS Helloworld example from previous post for this example.

1. Create a service interface "CountryService" and two concrete classes "CountryService1" and "CountryService2" (Business services)

package vn.nvanhuong.servicelocator.service;

import java.util.List;

public interface CountryService{
 public List<String> getCountries();
}

The first concrete service:
package vn.nvanhuong.servicelocator.service.impl;

import java.util.ArrayList;
import java.util.List;

import vn.nvanhuong.servicelocator.service.CountryService;

public class CountryService1 implements CountryService{

 public List<String> getCountries() {
  List<String> result = new ArrayList<String>();
  result.add("Vietname");
  result.add("Switzerland");
  result.add("Japan");
  return result;
 }

}

The second concrete service:
package vn.nvanhuong.servicelocator.service.impl;

import java.util.ArrayList;
import java.util.List;

import vn.nvanhuong.servicelocator.service.CountryService;

public class CountryService2 implements CountryService{

 public List<String> getCountries() {
  List<String> result = new ArrayList<String>();
  result.add("Vietname");
  result.add("America");
  result.add("China");
  return result;
 }

}

2. Create class "InitialContext" that is used for looking up and creating classes basing on the provided names
package vn.nvanhuong.servicelocator;

import java.lang.reflect.Constructor;

public class IntitialContext {
 
 public Object lookup(String serviceName){
    
  if(serviceName != null){
   try {
    Class<?> clazz = Class.forName(serviceName);
    Constructor<?> ctor = clazz.getConstructor();
    Object object = ctor.newInstance();
    return object;
   } catch (Exception ex) {
    ex.printStackTrace();
   } 
  }
  return null;
 }

}


3. Create class "ServiceLocator", I used Singleton to cache the object.
package vn.nvanhuong.servicelocator;

public class ServiceLocator {
 private static ServiceLocator instance;
 
 private ServiceLocator(){}
 
 public static synchronized ServiceLocator getInstance(){
  if(instance == null){
   return new ServiceLocator();
  }
  return instance;
 }

 public Object getService(String serviceName) {
  IntitialContext initialContext = new IntitialContext();
  return initialContext.lookup(serviceName);
 }

}

4. Create a resource file "services.properties" in order to configure the changing implementation at run-time. :)
country = vn.nvanhuong.servicelocator.service.impl.CountryService1
language = vn.nvanhuong.servicelocator.service.impl.LanguageService1

5. Calling SeviceLocator in Managed bean (Client)
package vn.nvanhuong.servicelocator.bean;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

import javax.faces.bean.ManagedBean;

import vn.nvanhuong.servicelocator.ServiceLocator;
import vn.nvanhuong.servicelocator.service.CountryService;

@ManagedBean(name = "helloBean")
public class HelloBean {
 private List<String> countries;
 
 public HelloBean() throws IOException {
  Properties prop = new Properties();
     InputStream input = null;
     
     String filename = "services.properties";
  input = HelloBean.class.getClassLoader().getResourceAsStream(filename);

  prop.load(input);
  CountryService countryService = (CountryService) ServiceLocator.getInstance()
              .getService(prop.getProperty("country"));
        this.countries = countryService.getCountries();
  
 }

 public List<String> getCountries() {
  return countries;
 }

 public void setCountries(List<String> countries) {
  this.countries = countries;
 }
 
}
6. GUI code: index.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
 <title>Service Locator</title>
</h:head>
<h:body>
 <h3>Lis of countries:</h3>
 <h:dataTable value="#{helloBean.countries}" var="country">
   <h:column>
         <h:outputText value="#{country}" />
     </h:column>
 </h:dataTable>

</h:body>
</html>

7. Test (on Tomcat v7.0)

http://localhost:8080/service_locator/

Use the "CountryService1" by changing in "services.properties", don't need to restart the server.

country = vn.nvanhuong.servicelocator.service.impl.CountryService1




Use the "CountryService2" by changing in "services.properties", don't need to restart the server.

country = vn.nvanhuong.servicelocator.service.impl.CountryService2

Here we also can add more concrete classes of CountryService and just declare in file "services.properties" for using. For this reason, I use Java Reflection to create the objects basing on the provided names in general way otherwise we have to change the InitialContext whenever we want to add new concrete class of "CountryService".

Note:
There might have several ways to implement this pattern but the idea is general so that my example is only a case. We also can improve my implementation by using caching technique, see reference [3].


Reference:

Comments

Popular posts from this blog

Junit - Test fails on French or German string assertion

In my previous post about building a regex to check a text without special characters but allow German and French . I met a problem that the unit test works fine on my machine using Eclipse, but it was fail when running on Jenkins' build job. Here is my test: @Test public void shouldAllowFrenchAndGermanCharacters(){ String source = "ÄäÖöÜüß áÁàÀâÂéÉèÈêÊîÎçÇ"; assertFalse(SpecialCharactersUtils.isExistSpecialCharater(source)); } Production code: public static boolean isExistNotAllowedCharacters(String source){ Pattern regex = Pattern.compile("^[a-zA-Z_0-9_ÄäÖöÜüß áÁàÀâÂéÉèÈêÊîÎçÇ]*$"); Matcher matcher = regex.matcher(source); return !matcher.matches(); } The result likes the following: Failed tests: SpecialCharactersUtilsTest.shouldAllowFrenchAndGermanCharacters:32 null A guy from stackoverflow.com says: "This is probably due to the default encoding used for your Java source files. The ö in the string literal in the J...

How to apply Lean - Kanban for your business

This is the topic of Scrum Breakfast meetup this time, speaker: Ms. Phuong Bui - Technical Project Manager of YOOSE Pte. Ltd. http://www.meetup.com/Scrum-Breakfast-Vietnam-Agile-and-Scrum-Meetup/events/230313727/ Lean comes from Lean manufacturing is a method that focuses on elimination of wastes. In other words, this is a set of principles for archiving the quality, speed and customer alignment. The first time I knew about the term "Lean" is  from the book Software Craftsmanship . Sandro recommends if we want to transform our pet projects into a real business, we should get familiar with Lean Startup concepts. In this talk, Ms. Phuong pointed out some major wastes includes information (ex: unclear requirements), processes (ex: waiting), physical environment and people. Knowing what the problems should be the best way to eliminate them. The difference between  Single item flow and Batch processing is the second main point; and it is the Lean's idea. Batch pr...

The HelloWorld example of JSF 2.2 with Myfaces

I just did by myself create a very simple app "HelloWorld" of JSF 2.2 with a concrete implementation Myfaces that we can use it later on for our further JSF trying out. I attached the source code link at the end part. Just follow these steps below: 1. Create a Maven project in Eclipse (Kepler) with a simple Java web application archetype "maven-archetype-webapp". Maven should be the best choice for managing the dependencies , so far. JSF is a web framework that is the reason why I chose the mentioned archetype for my example. 2. Import dependencies for JSF implementation - Myfaces (v2.2.10) into file pom.xml . The following code that is easy to find from  http://mvnrepository.com/  with key words "myfaces". <dependency> <groupId>org.apache.myfaces.core</groupId> <artifactId>myfaces-api</artifactId> <version>2.2.10</version> </dependency> <dependency> <groupId>org.apache.myfaces.core<...

Installing NGINX on macOS

I have heard of a lot of NGINX recently. One of them was it can help for security issues; for sure, it much be more. It so happens that our team has got a ton of user stories from a security audit. It's time to delve into it. What is NGINX? In order to get a basic idea and have some fun , I've just picked some available posts from my favorite Vietnamese blogger communities as below: https://kipalog.com/posts/Cau-hinh-nginx-co-ban---Phan-1 https://viblo.asia/hoang.thi.tuan.dung/posts/ZabG912QGzY6 NGINX (pronounce: Engine-X) is a web server (comparing to IIS, Apache). It can be used as a reverse proxy ( this is what I need for security issues with configuration ), load balancer and more. How to get started? I found the below path for learning NGINX by googling "learn nginx": https://www.quora.com/What-are-some-good-online-resources-to-learn-Nginx In this post, I only went first step. This is installing NGINX on macOS and taking a first look at the confi...

MS SQL Server Views

"Creates a virtual table whose contents (columns and rows) are defined by a query. Use this statement to create a view of the data in one or more tables in the database. For example, a view can be used for the following purposes: - To focus, simplify, and customize the perception each user has of the database. - As a security mechanism by allowing users to access data through the view, without granting the users permissions to directly access the underlying base tables. - To provide a backward compatible interface to emulate a table whose schema has changed." [1] Beside that, our team used view in order to improve the performance of our web apps when a database has a very complicated relationship between its tables by using ORM Frameworks such as Hibernate. Example code: --create CREATE VIEW placeholders AS SELECT EMPKEY AS empkey, CONNUMB AS connumb, EMPNBR AS empNbr, ACEEMPN AS empFirstName, ACEEMPFN AS empLastName, EMPNAM AS empFullName, ...