After listening to API, let’s see what SPI is

Time:2020-3-31

Quotation

Usually API can hear a lot? What is SPI? Don’t worry. Let’s first look at the call relationship of interface oriented programming to understand the similarities and differences between API and SPI.

SPI understanding

Let’s start with an official introduction: the full name of SPI (service provider interface) is a built-in service provision and discovery mechanism in JDK. (after listening to this, I feel confused.) ok, let’s understand it with pictures.
After listening to API, let's see what SPI is
In short, it can be divided into caller, interface and service party. Interface is protocol and contract, which can be defined by the caller or by the service party. That is to say, interface can be located in the caller’s package or the service party’s package
1. When the definition and implementation of the interface are in the service side, only when the interface is exposed to the caller, we call it API;
2. When the interface is defined by the caller (implemented by the server), we also give it a name — SPI.
It should be easy to understand, right?

Use scenarios of SPI

SPI is widely used in the framework. Here are a few examples:
1. Select MySQL driver driver manager to determine the driver to be used according to the configuration;
2. Extension mechanism in Dubbo framework (Dubbo official website link)

Using examples

After reading the above introduction and the application of SPI in the framework, I think there has been a prototype of SPI in the reader’s brain, talk is heap! Show me the code

1. First define an interface, ninja service interface

public interface NinjaService {
    void performTask();
}

2. Next write two implementation classes, forbearanceserviceimpl (upper tolerance) and shinobuserviceimpl (lower tolerance)

public class ForbearanceServiceImpl implements NinjaService {

    @Override
    public void performTask() {
        System. Out. Println ("Shangren is performing A-level task");
    }
}
public class ShinobuServiceImpl implements NinjaService {

    @Override
    public void performTask() {
        System. Out. Println ("xiaren is performing level D tasks");
    }
}

3. Next, we create the meta-inf / services directory under main / resources /, and a file of com.scott.java.task.spi.ninjaservice (full name of Ninja service class) under the services directory

4. Create a client scenario class to call to see the result
After listening to API, let's see what SPI is
It perfectly calls the performtask () method of two implementation classes

5. Finally, paste the directory structure
After listening to API, let's see what SPI is
Attach the address of a wave of code examples, in SPI, GIT link;

Simple analysis of SPI source code

1. First look at the definition and properties of the core class serviceloader

//It is used when inheriting iteratable class traversal
public final class ServiceLoader<S> implements Iterable<S>
{
  //This is why you need to create service class files in meta-inf / services /
  private static final String PREFIX = "META-INF/services/";

  //Loaded services
  private final Class<S> service;

  //Classloader
  private final ClassLoader loader;

  //Access control class
  private final AccessControlContext acc;

  //The cache of implementation classes is based on the order of initialization, that is, the order of loading defined in the / services / file
  private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

  //Lazy loading iterator
  private LazyIterator lookupIterator;

2. Start with the client, and then debug in sequence

ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);
public static <S> ServiceLoader<S> load(Class<S> service) {
      //Get the current classloader, appclassloader
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return ServiceLoader.load(service, cl);
  }
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
       return new ServiceLoader<>(service, loader);
   }

The latter one is omitted, because here is just the initialization based on ninjaservice. There is nothing difficult to understand

3. Let’s take a look at the specific calling process. The class file corresponding to the client is used here, because adding for (foreach) is a syntactic sugar in Java, which is actually the content after compilation

public static void main(String[] args) {
       ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);
      //Here is to add the code for sugar dissolving. If you are interested, you can learn about Java's syntax sugar
       Iterator var2 = ninjaServices.iterator();

       while(var2.hasNext()) {
           NinjaService item = (NinjaService)var2.next();
           item.performTask();
       }
   }

4. As the breakpoint continues, we enter the method of var2. Hasnext()

public boolean hasNext() {
            //Knownproviders haven't been loaded yet. Go to the following branch
             if (knownProviders.hasNext())
                 return true;
             return lookupIterator.hasNext();
         }

Here, the properties of serviceloader above lookupiterator are introduced. It is actually an internal class of iterator in serviceloader. Then the hasNext () method of the internal class Iterator is called.

public boolean hasNext() {
          //   acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
          //Serviceloader initialization has not set securitymanager, so ACC is null, enter hasnextservice()
           if (acc == null) {
               return hasNextService();
           } else {
               PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                   public Boolean run() { return hasNextService(); }
               };
               return AccessController.doPrivileged(action, acc);
           }
       }

5. Hasnextservice() analysis

private boolean hasNextService() {
           if (nextName != null) {
               return true;
           }
           if (configs == null) {
               try {
               //Here, the file under meta-inf / services is loaded, that is, the file containing two implementation classes with fully qualified names
                   String fullName = PREFIX + service.getName();
                   if (loader == null)
                       configs = ClassLoader.getSystemResources(fullName);
                   else
                   //Because the loader is a non null appclassloader
                       configs = loader.getResources(fullName);
               } catch (IOException x) {
                   fail(service, "Error locating configuration files", x);
               }
           }
           while ((pending == null) || !pending.hasNext()) {
               if (!configs.hasMoreElements()) {
                   return false;
               }
               //Here are the files of the two implementation classes in the file loaded above
               pending = parse(service, configs.nextElement());
           }
           nextName = pending.next();
           return true;
       }

6. Continue to see the parse method. The last thing returned here is iterator < string > with two fully qualified class names. In fact, it is to load the file contents under services /

private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
   InputStream in = null;
   BufferedReader r = null;
   ArrayList<String> names = new ArrayList<>();
   try {
       in = u.openStream();
       r = new BufferedReader(new InputStreamReader(in, "utf-8"));
       int lc = 1;
       while ((lc = parseLine(service, u, r, lc, names)) >= 0);
   } catch (IOException x) {
       fail(service, "Error reading configuration file", x);
   } finally {
       try {
           if (r != null) r.close();
           if (in != null) in.close();
       } catch (IOException y) {
           fail(service, "Error closing configuration file", y);
       }
   }
   return names.iterator();
}

Incidentally say parseline (service, u, R, LC, names), check whether the class name conforms to the specification. If it conforms, add it to the iterator. Here, var2
. hasnext() finished executing. As a result, the file contents under services are loaded

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                         List<String> names)
       throws IOException, ServiceConfigurationError
   {
       String ln = r.readLine();
       if (ln == null) {
           return -1;
       }
       int ci = ln.indexOf('#');
       if (ci >= 0) ln = ln.substring(0, ci);
       ln = ln.trim();
       int n = ln.length();
       if (n != 0) {
           if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
               fail(service, u, lc, "Illegal configuration-file syntax");
           int cp = ln.codePointAt(0);
           if (!Character.isJavaIdentifierStart(cp))
               fail(service, u, lc, "Illegal provider-class name: " + ln);
           for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
               cp = ln.codePointAt(i);
               if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                   fail(service, u, lc, "Illegal provider-class name: " + ln);
           }
           if (!providers.containsKey(ln) && !names.contains(ln))
               names.add(ln);
       }
       return lc + 1;
   }
private boolean hasNextService() {
          if (nextName != null) {
              return true;
          }
          if (configs == null) {
              try {
                  String fullName = PREFIX + service.getName();
                  if (loader == null)
                      configs = ClassLoader.getSystemResources(fullName);
                  else
                      configs = loader.getResources(fullName);
              } catch (IOException x) {
                  fail(service, "Error locating configuration files", x);
              }
          }
          while ((pending == null) || !pending.hasNext()) {
              if (!configs.hasMoreElements()) {
                  return false;
              }
              pending = parse(service, configs.nextElement());
          }
          //Here, the name of the next implementation class is assigned to the nextname property of lazyiterator
          nextName = pending.next();
          return true;
      }

7. Next, execute the next (method) of ninjaservice item = (ninjaservice) var2. Next(), and then continue debugging. Here I omit some method calls, only showing the useful method, which is the nextservice () method of the internal class lazyiterator of serviceloader

private S nextService() {
           if (!hasNextService())
               throw new NoSuchElementException();
           String cn = nextName;
           nextName = null;
           Class<?> c = null;
           try {
           //Here nextname has been assigned above, so reflection creates an instance
               c = Class.forName(cn, false, loader);
           } catch (ClassNotFoundException x) {
               fail(service,
                    "Provider " + cn + " not found");
           }
           //Type judgment
           if (!service.isAssignableFrom(c)) {
               fail(service,
                    "Provider " + cn  + " not a subtype");
           }
           try {
           //Strong transition type
               S p = service.cast(c.newInstance());
          //Add the class to the providers property of serviceloader and return
               providers.put(cn, p);
               return p;
           } catch (Throwable x) {
               fail(service,
                    "Provider " + cn + " could not be instantiated",
                    x);
           }
           throw new Error();          // This cannot happen
       }

8. When the implementation class of the subclass returns, the analysis is over

Conclusion:

1. Know what SPI is;
2. Simple difference and connection between SPI and API;
3. Learn how to use SPI to expand services;
4. The source code loading process of serviceloader is analyzed. It’s a simple matter that meta-inf / services defines the interface (file name) and implementation class (file content) to be implemented,
Serviceloader does not instantiate the implementation class when loading, but creates the instance with reflection when iterator traverses

If you think it’s OK, you can like it and pay attention to it. You will continue to write better articles ~ XD

reference material:

1.http://cr.openjdk.java.net/~m…
2.https://www.cnblogs.com/happy…
3.http://dubbo.apache.org/zh-cn…
4.https://www.cnblogs.com/googl…
5.https://zhuanlan.zhihu.com/p/…

Recommended Today

Configure Apache to support PHP in the Apache main configuration file httpd.conf Include custom profile in

In Apache’s main configuration file / conf/ http.conf Add at the bottom Include “D:workspace_phpapache-php.conf” The file path can be any In D: workspace_ Create under PHP file apache- php.conf file Its specific content is [html] view plain copy PHP-Module setup LoadFile “D:/xampp/php/php5ts.dll” LoadModule php5_module “D:/xampp/php/php5apache2_2.dll” <FilesMatch “.php$”> SetHandler application/x-httpd-php </FilesMatch> <FilesMatch “.phps$”> SetHandler application/x-httpd-php-source </FilesMatch> […]