Creating Extensible Applications using service-loaders according to a singleton design pattern

我的梦境 提交于 2020-05-16 03:11:34

问题


I'm working on project on IntelliJ IDEA and I want to add support to Extensible Applications in my java application.

The way to do it is, creating a jar file in this jar file there should be a META-INF/services directory, inside this directory, I need to add a file whose name contains the same name as the fully-qualified name of the interface which provides the service, and in that file it should have the fully-qualified name of the implementations of that interface.

This is the encrypt and decrypt service in my project .

These two are the files in my program which provides the service.

This is the interface which declares the service methods.

package serviceLoader;

public interface Cipher {

     byte[] encrypt(byte[] source, byte[] key);
     byte[] decrypt(byte[] source, byte[] key);
     int strength();

}

This is the implementation class for those services.

package serviceLoader.impl;

import serviceLoader.Cipher;

public class CaesarCipher implements Cipher {

  public byte[] encrypt(byte[] source, byte[] key)   {

    var result = new byte[source.length];
    for (int i = 0; i < source.length; i++)
      result[i] = (byte)(source[i] + key[0]);
    return result;

  }

  public byte[] decrypt(byte[] source, byte[] key)   {

    return encrypt(source, new byte[] { (byte) -key[0] });

  }

  public int strength() {

    return 1;

  }

}

I want to know how to create this program according to a singleton-design pattern, and do I need any additional java files to to achieve singleton-design pattern?


回答1:


You need a class that locates, loads and instantiates service providers (specific implementations of Cipher) and catch them.

There are some approach to implementing singletons. One of the most common way is based on keeping the constructor private and exporting a public static member to provide access to the sole instance. This public static member can be a static field or a static method factory (getInstance() in our sample code):

import serviceLoader.Cipher;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;

public final class CipherManager {
  private final static ArrayList<Cipher> ciphers = new ArrayList<>();
  private final static CipherManager INSTANCE = new CipherManager();

  private CipherManager() {
    load();
  }

  private static void load() {
    ServiceLoader<Cipher> cipherLoader = ServiceLoader.load(Cipher.class);
    Iterator<Cipher> ciphersIterator = cipherLoader.iterator();
    List<Cipher> cipherProviders = new ArrayList<>();
    ciphersIterator.forEachRemaining(cipherProviders::add);
    updateCiphers(cipherProviders);
  }

  static synchronized void updateCiphers(List<Cipher> cipherProviders) {
    ciphers.clear();
    ciphers.addAll(cipherProviders);
  }

  public static CipherManager getInstance() {
    return INSTANCE;
  }

  public void reload() {
    load();
  }

  public Cipher getCipher() {
    if (!ciphers.isEmpty()) {
      return ciphers.get(0);
    }
    return null;
  }
}

The getCipher() method should be implemented as you need. Maybe you want to get a privoder by the class name or any unique property of your service (e.g. provider id, name, etc). With these in mind, I prefer to use a HashMap in order to catch the service providers (instead of ArrayList).

private final static HashMap<String, Cipher> ciphers = new HashMap<>();

Another way to implement a singleton is to declare a single-element enum:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceLoader;

public enum CipherManager {
  INSTANCE;

  private final static HashMap<String, Cipher> ciphers = new HashMap<>();

  static {
    load();
  }

  public static CipherManager getInstance() {
    return INSTANCE;
  }

  public void reload() {
    load();
  }

  private static void load() {
    ServiceLoader<Cipher> cipherLoader = ServiceLoader.load(Cipher.class);
    Iterator<Cipher> ciphersIterator = cipherLoader.iterator();
    HashMap<String, Cipher> cipherProviders = new HashMap<>();
    ciphersIterator.forEachRemaining(cipher -> cipherProviders.put(cipher.getClass().getName(), cipher));
    updateCiphers(cipherProviders);
  }

  static synchronized void updateCiphers(Map<String, Cipher> cipherProviders) {
    ciphers.clear();
    ciphers.putAll(cipherProviders);
  }

  public Cipher getCipher(String className) {
    return ciphers.get(className);
  }
}

In this approach, getInstance() method is added to denote the CipherManager follows singleton pattern. So, getInstance() method can be removed from the code and we can use the INSTANCE field directly .



来源:https://stackoverflow.com/questions/60846599/creating-extensible-applications-using-service-loaders-according-to-a-singleton

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!