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

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, ...

If We Want to Go Fast, We Need to Go Well

Have you ever thought that we won't need to code anymore because programs might be generated from specification? The answer can be yes or no; there is still arguing about it. The programming language is more and more closed to the requirements. The starting is from a very low level as Assembly to a very high level like Python. However, it doesn't make much sense when saying that we will eliminate coding. For me, we currently still need to express our ideas in exact words that tells the machine what we want. Otherwise, I hope in the future the machine is intelligent enough to understand our requirements directly from our words. ;) Take a look at the famous quote of Robert C.Martin about what I mentioned above: "Remember that code is really the language in which we ultimately express the requirements. We may create languages that are closer to the requirements. We may create tools that help us parse and assemble those requirements into formal structures. But we wi...

Make simple music program with beep(freq, duration) with Pascal

Pascal is my first programing language when I have studied in high school. It was really exciting for me. :) The Pascal programming language was created by Niklaus Wirth in 1970. It was named after Blaise Pascal, a famous French Mathematician. It was made as a language to teach programming and to be reliable and efficient. Pascal has since become more than just an academic language and is now used commercially . I tried to make a simple music program by using Lazarus IDE on MS Window 7, 64-bit. It frustrated me a few times how difficulty to use Sound command to make a sound. Sound did not work on my compiler and my platform anymore. So far, I just could use beep(freq, duration) from window unit to implement my work. Here is my code. ;) program mysong; uses Windows, crt; const C: Integer = 512; { x = A * EXP(LN(2)/12)} C_: Integer = 542; D: Integer = 574; D_: Integer = 608; E: Integer = 644; F: Integer = 682; F_: Integer = 723; G: Integer = ...

My must-have apps for daily work

There is no doubt that cool apps can help us be more productive and enjoyable at work. For the time being, I really love the following apps which are used by me almost every day. 1. A personal Kanban In fact, a personal kanban is the most useful app for me. Why does it matter? It is not just a to-do list, but it keeps me motivated every day because it helps me be able to know what my "big picture" is. I usually set up my plans together with a path to reach them.  KanbanFlow  is my preferred tool. KanbanFlow 2. A terminal Needless to say, a terminal is a must-have app for every developer, especially the ones use macOS/Linux. Due to its importance, I love to decorate and enhance it to be super exciting with various tools such as  iTerm ,  oh-my- zsh , and  thefuck . ;) iTerm + oh-my-zsh 3. A documentation "ecosystem" As a developer, I can not remember all things that I have experimented a day. Moreover, a document is really useful for sharing an...