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

Gzip upload on browsers

Today, I faced a problem that I could not upload my archive file with gzip format on Firefox, even it worked on Chrome. I was using macOS. My application had a setting to whitelist accepted files. I’ve already added "application/gzip" to that list. "It’s strange!", I thought. I finally figured out that my uploaded file's type actually was "application/x-gzip" on Firefox. I also asked my colleagues to check their uploaded files on Window and Ubuntu. Hmm… they were totally different! It was "application/x-compressed" on Window, and was "application/x-compressed-tar" on Ubuntu. In fact, gzip is already standardized by IANA. There is a note in RFC-6713 as below: "Some applications have informally used media types such as application/gzip-compressed, application/gzipped, application/x-gunzip, application/x-gzip, application/x-gzip-compressed, and gzip/document to describe data compressed with gzip. The media types defin...

Java 8 - Persistent data structure

The following is a series of posts about "functional programming in Java" which is the result of my understanding by reading the book " Java 8 in Action: Lambdas, Streams, and Functional-style Programming, by Alan Mycroft and Mario Fusco ". 1. Why functional programming? 2. Functional programming in Java 8 3. Java 8 - Using Functions as Values 4. Java 8 - Persistent data structure Persistent data structure is also known as a simple technique but it's very important. Its other names are functional data structure and immutable data structure. Why is it "persistent"? Their values persist and are isolated from changes happening elsewhere. That's it! This technique is described as below: If you need a data structure to represent the result of a computation, you should make a new one and not mutable an existing data structure. Destructive updates version public static A doSomething(A a){ a.setProp1("new value"); return...

DevOps Toolchain Enhancement

 Historically, our company ubitec had started with a customer project. Agile/Scrum was our proposal for working with customers. Time by time, Agile/Scrum also became our culture for software development. To be successful with this development approach, we somehow needed to have a fast release for customers (i.e. every one week). Back then, we had a build tool Jenkins which was responsible for having sprint release packages for our customers. The build job pipelines contain some steps such as gathering the artifacts, checking the code convention, running the tests, building docker images, and packaging an archived file (a zip file). The set of tools involved in a pipeline is roughly called a toolchain. It is just a part of a bigger process called the DevOps toolchain. Source: https://www.ibm.com/blogs/cloud-archive/2016/11/devops-architecture-available-on-bluemix-garage-method-site/ DevOps is a proven method that fits Agile. Today,  it is even treated as a mandatory factor...

Fulfilling Your Contribution Needs

Human resource management motivation Managing human today is quite different from the industrial age which treats people as just "chickens". Rather than people now are very important to the success of an organization. People are an organization's special resource. They should be encouraged to grow to contribute their effort and creativeness to their beloved working environment because the contribution is one of their most needs in life. Training people: getting rid of the ineffective model and adopting the new one The ineffective model of training people: Hiring new people --> giving them a crash course once --> expecting them working effectively.  That somehow makes sense but you're about to expect a luck because you do not really spend your effort for mentoring them. If they can work effectively, well...lucky you! Otherwise, you will blame that these people are ineffective and you let them go and hire the new ones. What a waste of time! The new effe...