Discuss ServiceLoader implementation principle in detail

  • 2020-06-12 09:16:52
  • OfStack

In java, it is very convenient to get the superclass or interface information based on one subclass, but it is not so easy to get all the implementation classes based on one interface.

One clumsy approach is to scan all class and jar packages for class, load it in with ClassLoader, and then determine if it is a subclass of a given interface. But obviously, it's not going to work, it's going to be too expensive.

java itself also provides a way to get a subclass of an interface using java.util.ServiceLoader #load(java.lang.Class) < S > ), but you can't get all of the subclasses of a given interface using this method directly.

A subclass of an interface needs to be configured to actively register with an interface in order to be loaded into a subclass using ServiceLoader, and the subclass needs to have an unreferenced constructor to be instantiated by ServiceLoader

Here are the steps for using ServiceLoader

1. Write Service


package com.mogujie.uni.sl;
/**
 * Created by laibao
 */
public interface Animal {
    void eat();
}  

2. Write the implementation class (note: the implementation class does not have to be in the same project as the interface and can exist in other jar packages)


package com.mogujie.uni.sl;
/**
 * Created by laibao
 */
public class Pig implements Animal {
  @Override
  public void eat() {
    System.out.println("Pig eating...");
  }
}

package com.mogujie.uni.sl;
/**
 * Created by laibao
 */
public class Dog implements Animal {
  @Override
  public void eat() {
    System.out.println("Dog eating...");
  }
}

3. Set up META-ES41en /services directory under classpath of the project where the implementation class is located. The directory is fixed, and 1 must be created according to the specified name

Then according to the interface name in the directory to create a file, such as in the example above the interface name is com mogujie. uni. sl. Animal, it needs to be in the implementation class engineering build META INF/services/com mogujie. uni. sl. Animal such a file, and then configure the interface implementation class in this file, if there are multiple implementation classes that interface, 1 line 1 (split with a newline), such as:


com.mogujie.uni.sl.Pig
com.mogujie.uni.sl.Dog

4, you can use to obtain com ServiceLoader way. mogujie. uni. sl. Animal interface of all the subclasses.

The test classes are as follows:


package com.mogujie.uni;
import com.mogujie.uni.sl.Animal;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
 * Created by laibao
 */
public class TestServiceLoader {
  public static void main(String[] args) {
    ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
    Iterator<Animal> animalIterator = serviceLoader.iterator();
    while(animalIterator.hasNext()){
      Animal animal = animalIterator.next();
      animal.eat();
    }
  }
}

The output is as follows:


Pig eating...
Dog eating...

The principle of ServiceLoader is very simple, according to the given parameters (interface) can locate the interface and implementation class mapping configuration file path, and then read the configuration file, can get the interface subclass

The following is a self-implemented CustomServiceLoader with the same function as the system's ServiceLoader


package com.mogujie.uni;

import org.apache.commons.io.IOUtils;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by laibao
 */
public class CustomServiceLoader {

  public static final String MAPPING_CONFIG_PREFIX = "META-INF/services";

  public static <S> List<S> loade(Class<S> service) throws Exception{
    String mappingConfigFile = MAPPING_CONFIG_PREFIX + "/" + service.getName() ;
    // Due to the 1 There may be multiple implementation classes for an interface jar In the package META-INF Directory, so let's use it getResources return 1 a URL An array of 
    Enumeration<URL> configFileUrls = CustomServiceLoader.class.getClassLoader().getResources(mappingConfigFile);
    if(configFileUrls == null){
      return null ;
    }
    List<S> services = new LinkedList<S>();
    while(configFileUrls.hasMoreElements()){
      URL configFileUrl = configFileUrls.nextElement();
      String configContent = IOUtils.toString(configFileUrl.openStream());
      String[] serviceNames = configContent.split("\n");
      for(String serviceName : serviceNames){
        Class serviceClass = CustomServiceLoader.class.getClassLoader().loadClass(serviceName);
        Object serviceInstance = serviceClass.newInstance();
        services.add((S)serviceInstance);
      }
    }
    return services ;
  }

}

The test classes are as follows:


package com.mogujie.uni;
import com.mogujie.uni.sl.Animal;
import java.util.List;
/**
 * Created by laibao
 */
public class CustomServiceLoaderTest {
  public static void main(String[] args) throws Exception {
    List<Animal> animals = CustomServiceLoader.loade(Animal.class);
    for (Animal animal : animals){
      animal.eat();
    }
  }
}

Output:


Pig eating...
Dog eating...

java system defined ServiceLoader and our custom CustomServiceLoader loade method, their return value type is not the same, ServiceLoader loade method returns ServiceLoader object, ServiceLoader object implements Iterable interface, through ServiceLoader member method iterator(); We can iterate over all the service instances, while our custom CustomServiceLoader load method returns an List object, wrapping all the service instances in a collection.

By returning 1 Iterator object, the ServiceLoader of the system can do lazy loading on the service instance. Only when the iterator.next () method is called will the next service instance be instantiated, and only when it needs to be used will it be instantiated.


Related articles: